I used the new C++11 "enum class" type and observed a "undefined reference" problem when using g++. This probleme does not happen with clang++. I do not know if I am doing something wrong or if it is a g++ bug.

To reproduce the problem here is the code: (4 files: enum.hpp, enum.cpp, main.cpp and the Makefile)

// file: enum.hpp
enum class MyEnum {
  val_1,
  val_2
};

template<typename T>
struct Foo 
{
  static const MyEnum value = MyEnum::val_1;
};

template<>
struct Foo<int>
{
  static const MyEnum value = MyEnum::val_2;
};

template<typename T>
void foo(const T&);

and...

// file: enum.cpp 
#include <iostream>
#include "enum.hpp"

template<typename T>
void foo(const T&)
{
  switch(Foo<T>::value) {
  case MyEnum::val_1:
    std::cout << "\n enum is val_1"; break;
  case MyEnum::val_2:
    std::cout << "\n enum is val_2"; break;
  default:
    std::cout << "\n unknown enum"; break;
  }
}

// Here we force instantation, thus everything should be OK!?!
//
template void foo<int>(const int&);
template void foo<double>(const double&);

and...

// file: main.cpp
#include "enum.hpp"

int
main()
{
  foo(2.);
  foo(2);
}

and the Makefile...

COMPILER = g++ # does no work
#COMPILER = clang++ # Ok

all: main

main : main.cpp enum.cpp
    $(COMPILER) -std=c++11 -c enum.cpp -o enum.o
    $(COMPILER) -std=c++11 main.cpp enum.o -o main

When I am using g++ I get:

make -k 
g++  -std=c++11 -c enum.cpp -o enum.o
g++  -std=c++11 main.cpp enum.o -o main
enum.o: In function `void foo<int>(int const&)':
enum.cpp:(.text._Z3fooIiEvRKT_[_Z3fooIiEvRKT_]+0xe): undefined reference to `Foo<int>::value'
enum.o: In function `void foo<double>(double const&)':
enum.cpp:(.text._Z3fooIdEvRKT_[_Z3fooIdEvRKT_]+0xe): undefined reference to `Foo<double>::value'
collect2: error: ld returned 1 exit status
make: *** [main] Error 1
make: Target `all' not remade because of errors.

But with clang++ everything is fine (no compilation error).

Any explanation is welcome, because I am lost here.

Thanks! :)


About my config:

g++ --version
g++ (Debian 4.7.2-5) 4.7.2
Copyright (C) 2012 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

clang++ --version
Debian clang version 3.0-6 (tags/RELEASE_30/final) (based on LLVM 3.0)
Target: x86_64-pc-linux-gnu
Thread model: posix

uname -a
Linux IS006139 3.2.0-4-amd64 #1 SMP Debian 3.2.35-2 x86_64 GNU/Linux
有帮助吗?

解决方案

The reason you are getting those errors is because g++ expects your static variables to be defined somewhere.

There are a couple of different ways to fix this:

Since you are using an integral type, you can change your structures to inherit from integral_constant.

template<typename T>
struct Foo : std::integral_constant<MyEnum, MyEnum::val_1>
{
};

template<>
struct Foo<int> : std::integral_constant<MyEnum, MyEnum::val_2>
{
};

You can also declare the variables constexpr

template<typename T>
struct Foo
{
  static constexpr MyEnum value = MyEnum::val_1;
};

template<>
struct Foo<int>
{
  static constexpr MyEnum value = MyEnum::val_2;
};

You can define the static variables in your header file.

template<typename T>
struct Foo
{
  static const MyEnum value = MyEnum::val_1;
};

template<typename T>
const MyEnum Foo<T>::value;

template<>
struct Foo<int>
{
  static const MyEnum value = MyEnum::val_2;
};

// enum.cpp
const MyEnum Foo<int>::value;

其他提示

This is a bug in g++. A definition for a static data member is required if that data member is odr-used, but the only mention of Foo<int>::value or Foo<double>::value is here:

switch(Foo<T>::value) {

Per [basic.def.odr]p2 and p3, this is not an odr-use of Foo<T>::value because:

  • The lvalue-to-rvalue conversion is immediately applied to the expression Foo<T>::value, and
  • The entity Foo<T>::value is in the set of potential results of the expression Foo<T>::value, and
  • Foo<T>::value satisfies the requirements for appearing in a constant expression

Therefore no definition of Foo<int>::value nor Foo<double>::value is required in this program.

However, you would be well-advised to always define your static data members, like so:

// In your header file
template<typename T> const MyEnum Foo<T>::value;
// In your .cpp file
template<> const MyEnum Foo<int>::value;

The first one must go in the header, because any user of the template may need to instantiate it; duplicate instantiations of it will be merged. The second one must not go in a header -- there can be only a single definition of this entity in the entire program, because it is not templated (and it is not an inline function or class or enumeration definition, which also allow multiple definitions in different translation units).

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top