1

I have the following code:

#include <iostream>
template<int I>
class A
{
public:
    inline constexpr static int size() { return I; }
};

template<typename T>
inline constexpr auto size(const T& arg) noexcept -> decltype(arg.size())
{
    return arg.size();
}

template<typename T>
inline constexpr void twoLevel(const T& arg) noexcept
{
    static_assert(size(arg) > 0);
}

int main()
{
    A<5> a;
    static_assert(size(a)>0); //this works
    twoLevel(a); // this does not
    return 0;
}

Which fails to compile on msvc with the error expression did not evaluate to a constant, but works with gcc. Is it gcc accepting something that's undefined behaviour? Or is it a compiler bug on msvc's part? Here's a demo: godbolt code

Barry
  • 286,269
  • 29
  • 621
  • 977
lightxbulb
  • 1,251
  • 12
  • 29

1 Answers1

1

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;
  • [...]

In:

static_assert(size(arg) > 0);

We have an id-expression that refers to a variable of reference type, and the reference does not have preceding initialization, so we do not have a constant expression.

I think this:

static_assert(size(a) > 0);

works because of "preceding initialization" - we enter constant evaluation by directly binding the reference arg to the variable a, whereas in the other case we're binding a reference to another reference.

Both should work if you take by value though.

Community
  • 1
  • 1
Barry
  • 286,269
  • 29
  • 621
  • 977
  • So gcc shouldn't compile this either? Won't passing it by value make a copy though - is there a way to avoid this? – lightxbulb Jul 04 '19 at 16:14
  • 1
    An initialization of a function parameter does count as a preceding initialization, or else the final line `constexpr int y = h(1);` of the example in the same paragraph could not be valid. The issue is that `a` is not "usable in constant expressions" and its lifetime did not begin within evaluation of `size(arg) > 0`. – aschepler Jul 04 '19 at 16:35
  • @aschepler Is there any way to mitigate this problem, without passing the object by value? Since I do not want the object copied (it may contain a large static array). – lightxbulb Jul 04 '19 at 16:40
  • @aschepler That doesn't help me for my use case - I specifically needed a free function since I want to be able to define a specialization for structures (like plain arrays), that works even if they do not provide a size static method. – lightxbulb Jul 04 '19 at 16:59
  • 1
    @lightxbulb Then define a trait struct, which can be explicitly and partially specialized? – aschepler Jul 04 '19 at 17:41
  • @aschepler I was trying to avoid code duplication - I know how to deal with this if I don't use the above, it's just I felt it was inelegant duplicating code, especially when I feel like when everything is constant, it should be treated as such - but it seems my intuition disagrees with the standard (though gcc compiles this for some reason). – lightxbulb Jul 04 '19 at 18:00