26

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).

Community
  • 1
  • 1
void-pointer
  • 14,247
  • 11
  • 43
  • 61
  • Did you define the variable? Or what is the error message? – Columbo Jun 03 '14 at 15:45
  • @Arcoth I don't think he has provided a definition, the error goes away if you do. [Here's](http://coliru.stacked-crooked.com/a/92765cc8cb900ddf) the error message. I *think* the question is whether referring to `foo::value` constitutes odr-use. gcc seems to think yes, while clang thinks no. – Praetorian Jun 03 '14 at 15:59
  • @Praetorian Yes, sorry, I stupidly forgot to include the actual error message. – void-pointer Jun 03 '14 at 18:14

3 Answers3

18

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).

Filip Roséen - refp
  • 62,493
  • 20
  • 150
  • 196
  • **Note**: `template struct foo { static constexpr auto value = a; } template constexpr decltype (foo::value) foo::value;` should work as a *workaround*, but latest clang rejects it. currently investigating if it's a bug or not. – Filip Roséen - refp Jun 03 '14 at 17:20
  • Thanks for the comprehensive answer and workarounds. Did you file a report for the original bug in GCC yet? If you like, you can go ahead and do it. Otherwise, I can do the honors. – void-pointer Jun 03 '14 at 18:37
  • @void-pointer Honestly I was just about to do it, do you wanna write one up? If so; it's all yours. – Filip Roséen - refp Jun 03 '14 at 18:42
  • You can do it if you prefer; it's totally up to you. I was just offering in case you would rather someone else do it. – void-pointer Jun 03 '14 at 19:15
  • @FilipRoséen-refp I am not sure if `[basic.link]p8` is really relevant here. It says "_A type without linkage shall not be used as the type of a **variable** or function_...", where in the standard is it mentioned that enumerator name is a variable? – Cheshar May 15 '20 at 10:39
10

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;
Columbo
  • 60,038
  • 8
  • 155
  • 203
5

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;
}
Csq
  • 5,775
  • 6
  • 26
  • 39