23

constexpr functions are not supposed to contain:

A definition of a variable of non-literal type

But in this answer a lambda is defined in one: https://stackoverflow.com/a/41616651/2642059

template <typename T>
constexpr auto make_div(const T quot, const T rem)
{
    return [&]() {
        decltype(std::div(quot, rem)) result;
        result.quot = quot;
        result.rem = rem;
        return result;
    }();
}

and in my comment I define a div_t in one: How can I Initialize a div_t Object?

template <typename T>
constexpr decltype(div(T{}, T{})) make_div(const T quot, const T rem)
{
    decltype(div(T{}, T{})) x{};
    x.quot = quot;
    x.rem = rem;
    return x;
}

Exactly what is meant by the prohibition of the "definition of a variable of non-literal type"?

Visual Studio 2015 won't allow my definition of a div_t but I find it nonsensical that it would be allowable to just wrap such illegitimate behavior in a lambda and execute it. I'd like to know which if either of the compilers are behaving correctly with respect to the div_t definition.

Community
  • 1
  • 1
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • 2
    What version of the C++ language? The requirements were relaxed quite a bit from 2011. – rubenvb Jan 12 '17 at 16:45
  • @rubenvb Good question, I've edited. But C++14. – Jonathan Mee Jan 12 '17 at 16:50
  • 4
    A lambda expression is a expression, not a variable definition. But until C++17, a lambda expression cannot appear in a [constant expression](http://en.cppreference.com/w/cpp/language/constant_expression). Indeed, [when the context requires a constant expression, MSVC will complain](http://rextester.com/ZBRLBR12534). – cpplearner Jan 12 '17 at 16:57
  • @cpplearner So my (the first) example only compiles as the constexpr function is evaluated as const function? In my opinion the second example should not compile by definition. – Simon Kraemer Jan 12 '17 at 17:12
  • @cpplearner Incidentally if you don't try to assign the result of `make_div` to a `constexpr` variable your Visual Studio compiler error goes away. Perhaps `make_div` really isn't `constexpr` and the compiler's letting us get away with it? – Jonathan Mee Jan 12 '17 at 19:01
  • 3
    I don't think VS2015 supports C++14 extended `constexpr`. That explains your second function template since in C++11 `constexpr` functions are basically limited to a single return statement; it has nothing to do with "non-literal type" (and `div_t` is a literal type). The first one is ill-formed NDR pre-C++17. – T.C. Jan 12 '17 at 21:19
  • @T.C. Sounds like a workable answer to me if you can support that Visual Studio 2015 doesn't support C++14 `constexpr` functions. – Jonathan Mee Jan 12 '17 at 21:50
  • @rubenvb I've gone ahead and posted an answer that addresses, C++11, C++14, and C++17, so I've deleted the requirement the question. – Jonathan Mee Jan 13 '17 at 13:57
  • @JonathanMee That's great. I thought someone might want to do this five minutes after posting my pesky comment... – rubenvb Jan 13 '17 at 16:52

1 Answers1

29

It's virtually guaranteed that if there's a discrepancy gcc has the correct behavior, because Visual Studio 2015 doesn't support 's extension of constexpr: https://msdn.microsoft.com/en-us/library/hh567368.aspx#C-14-Core-Language-Features

C++11 constexpr functions

The function body can only contain:

  • null statements (plain semicolons)
  • static_assert declarations
  • typedef declarations and alias declarations that do not define classes or enumerations
  • using declarations
  • using directives
  • exactly one return statement

So cannot tolerate the definition of decltype(div(T{}, T{})) x{}. It would however be acceptable to roll the ternary suggested here in a constexpr function to achieve the same results:

template <typename T>
constexpr auto make_div(const T quot, const T rem)
{
    using foo = decltype(div(T{}, T{}));
                         
    return foo{1, 0}.quot != 0 ? foo{quot, rem} : foo{rem, quot};
}

Live Example

C++14 constexpr functions

The function body may contain anything but:

  • an asm declaration
  • a goto statement
  • a statement with a label other than case and default
  • a try-block
  • a definition of a variable of non-literal type
  • a definition of a variable of static or thread storage duration
  • a definition of a variable for which no initialization is performed

Where a "Literal Type" is defined here, specifically for objects though, they may be aggregate types with a trivial destructor. So div_t definitely qualifies. Thus , and by extension gcc, can tolerate the definition of decltype(div(T{}, T{})) x{}.

C++17 constexpr functions

C++17 added support for closure types to the definition of "Literal Type", so I find it strange that both gcc and Visual Studio support the use of the lambda in the return statement. I guess that's either forward looking support or the compiler chose to inline the lambda. In either case I don't think that it qualifies as a constexpr function.

[Source]

Community
  • 1
  • 1
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • 1
    @jnoathan_mee "The function body may contain anything but: ..." , we cannot call non-constexpr function in the body, right? – camino Apr 27 '18 at 19:48
  • @camino Correct, the result of this function must be calculable at compile-time, thus functions or parameters which cannot satisfy this requirement are not acceptable. That point isn't clearly documented though is it :( – Jonathan Mee Apr 27 '18 at 20:03