Different compilation results with template and non-template class having constexpr constructor

126 Views Asked by At

A constructor fails to quality as constexpr if the class contains std::vector as its data member since std::vector doesn't have constexpr constructor (atleast until C++17 standards, for C++20 I came across this draft). The issue is if the class itself is templatized, the code successfully gets compiled despite the fact that it has constexpr constructor and std::vector as data member.

Consider following two code snippets:

#include <vector>

class Y {
public:
  constexpr Y(){}
  std::vector<int> v{};
};

int main() {
  Y ob;
  return 0;
}

This code fails to compile whereas the following one compiles successfully

#include <vector>

template<typename T>
class X{
public: 
  constexpr X(){}
  std::vector<T> v;
};

int main() {
  X<int> ob;
  return 0;
}

I do have a slight feel that this might have to do something with how template class is compiled since when I declare a constexpr instance of X constexpr X<int> ob; , it fails to compile but unable to figure out what's actually going under the hood.

Edit: I am using GCC 8.3.1 . Following is the error message I got when compiling the first code snippet with C++17

error: call to non-‘constexpr’ function ‘std::vector<_Tp, _Alloc>::vector() [with _Tp = int; _Alloc = std::allocator<int>]’
   constexpr Y(){}
1

There are 1 best solutions below

0
user17732522 On

Before C++23 declaring a function (template) as constexpr which can never actually be called as part of a constant expression with any function or template arguments makes the program ill-formed, no diagnostic required (IFNDR). In other words the compiler is allowed to notice that the constexpr can never actually work and so may refuse to compile the program. I guess this was intended so that the compiler can tell the user about mistakes in writing functions that should be usable in constant expressions as early as possible. This was changed only recently for C++23 with P2448.

Before C++20, the default constructor for std::vector is not constexpr, but your constructor constexpr X(){} will use it. Therefore your constructor cannot ever be called as part of a constant expression evaluation and therefore the program is IFNDR. This applies to both examples.

However, because it is IFNDR, not just ill-formed, it is unspecified whether or not the program will compile. In fact IFNDR is essentially equivalent to undefined behavior for all inputs. The compiler could in theory do whatever it wants with the program, there are no standard-imposed requirements.

In fact recognizing that std::vector<T>'s default constructor is never constexpr when T is a template parameter is basically impossible. There could be any number of partial and explicit specializations of std::vector that declare the default constructor differently. The only thing that actually guarantees that none of them are declared constexpr is that the standard (before C++20) imposed that requirement.

Since C++20, the default constructor of std::vector is constexpr and so the code is well-formed and should compile in both examples.

And starting with C++23 it shouldn't matter whether or not the default constructor of the member is constexpr.