Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

`static constexpr auto` data-member initialized with unnamed enum

I was working on a C++11 project solely using clang++-3.4, and decided to compile using g++-4.8.2 in case there were any discrepancies in the errors produced. It turned out that g++ rejects some code that clang++ accepts. I have reduced the problem to the MWE given below.


enum { a };

template <class T>
struct foo
{
    static constexpr auto value = a;
};

int main()
{
    static constexpr auto r = foo<int>::value;
}

foo.cpp:5:23: error: ‘const<anonymous enum> foo<int>::value’, declared using anonymous type, is used but never defined [-fpermissive]

static const auto value = A;

I would like some help answering the following two questions:

  • Which compiler is correct in its interpretation of the standard? I am assuming that one compiler is right in either accepting or rejecting the code, and the other is wrong.

  • How can I work around this issue? I can't name the anonymous enum, because it is from a third-party library (in my case, the enums were Eigen::RowMajor and Eigen::ColMajor).

like image 492
void-pointer Avatar asked Jun 03 '14 15:06

void-pointer


People also ask

Is enum a constexpr?

There will be no noticeable difference for integral constants when used like this. However, enum is actually better, because it is a true named constant. constexpr integral constant is an object which can be, for example, ODR-used - and that would result in linking errors.

What does static constexpr mean?

static defines the object's lifetime during execution; constexpr specifies that the object should be available during compilation. Compilation and execution are disjoint and discontiguous, both in time and space.

What is constexpr in C ++ 11?

The keyword constexpr was introduced in C++11 and improved in C++14. It means constant expression. Like const , it can be applied to variables: A compiler error is raised when any code attempts to modify the value. Unlike const , constexpr can also be applied to functions and class constructors.

Why does constexpr need to be static?

A static constexpr variable has to be set at compilation, because its lifetime is the the whole program. Without the static keyword, the compiler isn't bound to set the value at compilation, and could decide to set it later.


3 Answers

Who's to blame?

GCC is inaccurately rejecting your snippet, it is legal according to the C++11 Standard (N3337). Quotations with proof and explanation is located the end of this post.

workaround (A) - add the missing definition

template <class T>
struct foo {
    static constexpr auto value = a;
    typedef decltype(a) value_type;
};

template<class T>
constexpr typename foo<T>::value_type foo<T>::value;


workaround (B) - use the underlying-type of the enumeration as placeholder

#include <type_traits>

template <class T>
struct foo {
  static const std::underlying_type<decltype(a)>::type value = a;
};

What does the Standard say? (N3337)

As stated, the snippet is legal C++11, as can be read in the following quoted sections.


When can we use a type without linkage?

[basic.link]p8 has detailed wording that describes when a type is "without linkage", and it states that an unnamed enumeration count as such type.

[basic.link]p8 also explicitly states three contexts where such a type cannot be used, but not one of the contexts apply to our usage, so we are safe.

A type without linkage shall not be used as the type of a variable or function with external linkage unless

  • the entity has C language linkage (7.5), or
  • the entity is declared within an unnamed namespace (7.3.1), or
  • the entity is not odr-used (3.2) or is defined in the same translation unit


Are you sure we can use auto in such context?

Yes, and this can be proven by the following quote:

7.1.6.4p auto specifier [dcl.spec.auto]

A auto type-specifier can also be used in declaring a variable in the condition of a selection statement (6.4) or an iteration statement (6.5), in the type-specifier-seq in the new-type-id or type-id of a new-expression (5.3.4), in a for-range-declaration, and in declaring a static data member with a brace-or-equal-initializer that appears within the member-specification of a class definition (9.4.2).

like image 116
Filip Roséen - refp Avatar answered Oct 21 '22 15:10

Filip Roséen - refp


Which compiler is correct in its interpretation of the standard?

gcc is incorrect. §9.4.2/3:

A static data member of literal type can be declared in the class definition with the constexpr specifier; if so, its declaration shall specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression. The member shall still be defined in a namespace scope if it is odr-used (3.2) in the program and the namespace scope definition shall not contain an initializer.

And the name is not odr-used as per §3.2:

A variable whose name appears as a potentially-evaluated expression is odr-used unless it is an object that satisfies the requirements for appearing in a constant expression (5.19) and the lvalue-to-rvalue conversion (4.1) is immediately applied.

This is indeed the case: It does satisfy the requirements for appearing in a constant expression and the lvalue-to-rvalue conversion is immediately applied (it is used as an initializer for an object). So GCC's rejection is incorrect.


A possible workaround is to define the member (but without a placeholder type). This definition is sufficient for both Clang and GCC:

template< typename T >
constexpr decltype(a) foo<T>::value;
like image 30
Columbo Avatar answered Oct 21 '22 15:10

Columbo


Workaround with decltype:

enum { a };

template <class T>
struct foo
{
    static constexpr auto value = a;
};

template <class T>
constexpr decltype(a) foo<T>::value;

int main()
{
    static constexpr auto r = foo<int>::value;
}
like image 5
Csq Avatar answered Oct 21 '22 17:10

Csq