11

Consider the following function:

template <size_t S1, size_t S2>
auto concatenate(std::array<uint8_t, S1> &data1, std::array<uint8_t, S2> &data2) {
    std::array<uint8_t, data1.size() + data2.size()> result;

    auto iter = std::copy(data1.begin(), data1.end(), result.begin());
    std::copy(data2.begin(), data2.end(), iter);

    return result;
}

int main()
{
    std::array<uint8_t, 1> data1{ 0x00 };
    std::array<uint8_t, 1> data2{ 0xFF };

    auto result = concatenate(data1, data2);
    return 0;
}

When compiled using clang 6.0, using -std=c++17, this function does not compile, because the size member function on the array is not constexpr due to it being a reference. The error message is this:

error: non-type template argument is not a constant expression

When the parameters are not references, the code works as expected.

I wonder why this would be, as the size() actually returns a template parameter, it could hardly be any more const. Whether the parameter is or is not a reference shouldn't make a difference.

I know I could of course use the S1 and S2 template parameters, the function is merely a short illustration of the problem.

Is there anything in the standard? I was very surprised to get a compile error out of this.

xskxzr
  • 12,442
  • 12
  • 37
  • 77
Martijn Otto
  • 878
  • 4
  • 21

3 Answers3

11

Because you have evaluated a reference. From [expr.const]/4:

An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:

  • ...
  • an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
    • it is usable in constant expressions or
    • its lifetime began within the evaluation of e;
  • ...

Your reference parameter has no preceding initialization, so it cannot be used in a constant expression.

You can simply use S1 + S2 instead here.

xskxzr
  • 12,442
  • 12
  • 37
  • 77
7

There has been a bug reported on this issue for clang titled: Clang does not allow to use constexpr type conversion in non-type template argument.

The discussion in it points that this is not really a bug.

An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:

  • [...]
  • an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
    • it is initialized with a constant expression or
    • its lifetime began within the evaluation of e;
  • [...]

The above quote is from [expr.const]/2.11 of draft n4659 with emphasis added.

P.W
  • 26,289
  • 6
  • 39
  • 76
0

Unfortunately, the standard states that in a class member access expression The postfix expression before the dot or arrow is evaluated;63 [expr.ref]/1. A postfix expression is a in a.b. The note is really interesting because this is precisely the case here:

63) If the class member access expression is evaluated, the subexpression evaluation happens even if the result is unnecessary to determine the value of the entire postfix expression, for example if the id-expression denotes a static member.

So data is evaluated even if it would not be necessary and the rule fore constant expression applies on it too.

Oliv
  • 17,610
  • 1
  • 29
  • 72
  • 1
    What's so unfortunate about `a.b` evaluating `a` (or `f().b` evaluating `f()`)? – T.C. Jan 10 '19 at 12:20
  • @T.C. When `b` is a static member, `a` will never be accessed! – Oliv Jan 10 '19 at 12:51
  • 1
    @T.C. But actualy I have just checked, there is a second design error in the definition of array! size is a non static member! That is crazy! There have been too many modification to the language. The entire design of the STL is obsolete. – Oliv Jan 10 '19 at 12:52
  • @T.C. 1) Why did the standard added "The postfix expression before the dot or arrow is evaluated"?? The fact a member is static or non-static is known at compile time in every translation unit where it is used. 2) Is there a paragraph in the library part of the standard that gives freedom to implementation to choose whether a member is implemented as a static member or non static one? I can not find any good reason to force this. – Oliv Jan 10 '19 at 13:09
  • What if for some reason you want to apply `&` operator to `size()` in generic code? If it is `static`, `&size` will be a function pointer, if it is non-`static`, it will be a member function pointer. For example, such generic code will not work with both "`static`-augmented" `std::array` and `std::vector` uniformly. Making `size()` a static member function may break some existing code. – Evg Nov 26 '19 at 11:33