constructor of derived class cannot be constexpr i

2019-06-25 09:09发布

问题:

I want to define constexpr values of a derived type (SBar), using a constructor whose only argument is a variable of the base class (SFoo), which is simply used initialize the base.

This works fine when the base class has no array member. However, when I add an array the derived values can no longer be constexpr. A simple copy of the base class does yield constexpr results, though.

I have explicitly defaulted all copy and move constructors just to be safe.

test.cpp

#define USE_ARRAY


struct SFoo
    {
        constexpr SFoo() =default;
        constexpr SFoo(SFoo const&) =default;
        constexpr SFoo(SFoo      &) =default;
        constexpr SFoo(SFoo     &&) =default;
        constexpr SFoo& operator = (SFoo const&) =default;
        constexpr SFoo& operator = (SFoo      &) =default;
        constexpr SFoo& operator = (SFoo     &&) =default;

#       ifdef USE_ARRAY
            constexpr SFoo(int const (&array)[1]) :
                M_array{array[0]}
                {}

            int M_array[1] = {0};
#       else
            constexpr SFoo(int value) :
                M_value{value}
                {}

            int M_value = 0;
#       endif
    };


struct SBar : SFoo
    {
        constexpr SBar() =default;
        constexpr SBar(SBar const&) =default;
        constexpr SBar(SBar      &) =default;
        constexpr SBar(SBar     &&) =default;
        constexpr SBar& operator = (SBar const&) =default;
        constexpr SBar& operator = (SBar      &) =default;
        constexpr SBar& operator = (SBar     &&) =default;

        constexpr SBar(SFoo foo) : SFoo(foo) {}
    };


// Instances:

#       ifdef USE_ARRAY
    constexpr int arg[1] = {3};
#       else
    constexpr int arg = 3;
#       endif

constexpr SFoo foo(arg); // base "value" constructor is constexpr.
constexpr SFoo foo2(foo); // base copy constructor is constexpr.
constexpr SBar bar(foo); // (line 54): this line fails.

compiling with

clang++ -std=c++1z -c -o test.o test.cpp 

yields

test.cpp:54:16: error: constexpr variable 'bar' must be initialized by a constant expression
constexpr SBar bar(foo);
               ^~~~~~~~
1 error generated.

however, everything works if I don't define USE_ARRAY.

Does anyone know why this is happening?

(I know std::array can help, but I'd rather use native arrays and understand the underlying problem).

回答1:

So for clang it looks like there are a couple of fixes for this. You can change:

constexpr SBar(SFoo foo) : SFoo(foo) {}

to take foo by const reference:

constexpr SBar(const SFoo &info) : SFoo(info) {}

The other fix that seems to work is comment out the following copy constructor in sFoo:

//constexpr SFoo(SFoo      &) =default;

I don't see immediately the language in the draft C++1z standard that make this change make sense.

On the other hand gcc complains about the copy constructor saying the implicit definitions would not be constexpr (see it live), for example:

error: explicitly defaulted function 'constexpr SFoo& SFoo::operator=(const SFoo&)' cannot be declared as constexpr because the implicit declaration is not constexpr
    constexpr SFoo& operator = (SFoo const&) =default;
                    ^

which is not obvious to me from my reading of 7.1.5 [dcl.constexpr] and 5.20 [expr.const].

As far as I can tell from my reading of section 12.8p26 the implicitly defined copy/move assignment should be constexpr. So gcc seems incorrect here.



回答2:

It's not due to the array member. It's due to the assignment operators. If you make the following changes the code will work for both CLANG and GCC:

struct SFoo {
  constexpr SFoo() = default;
  constexpr SFoo(SFoo const&) = default;
  constexpr SFoo(SFoo&&) = default;
  constexpr SFoo(int const (&array)[1]) : M_array{array[0]} {}

  int M_array[1] = {0};
};

struct SBar : SFoo {
  constexpr SBar() = default;
  constexpr SBar(SBar const&) = default;
  constexpr SBar(SBar&&) = default;
  constexpr SBar(SFoo info) : SFoo(info) {}
};

Live Demo

Your previous setting will work if instead of classical array you use a std::array:

struct SFoo {
  constexpr SFoo() = default;
  constexpr SFoo(SFoo const&) = default;
  constexpr SFoo(SFoo      &) = default;
  constexpr SFoo(SFoo     &&) = default;
  constexpr SFoo& operator = (SFoo const&) = default;
  constexpr SFoo& operator = (SFoo      &) = default;
  constexpr SFoo& operator = (SFoo     &&) = default;
  constexpr SFoo(std::array<int, 1> const &array) : M_array{array} {}

  std::array<int, 1> M_array = {};
};

Live Demo

No as for the reason why. I'm still searching...