Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ template static integer constants: out of class definition

This question is about the relationship between templates and static integral constants in Visual Studio C++ 2013 with flag /Za. It has implications for the boost library.


First, let us check the code without templates:

struct easy
{
    static const int a = 5;
    const int b;

    easy( int b_ ) : b( std::max( b_, a ) )
    {}
};

const int easy::a;

int main()
{
    easy d_Easy( 0 );
    return 0;
}

According to the manual page for compiler option /Za: "Under the standard (/Za), you must make an out-of-class definition for data members". The example in that page and the code above declares the static constant within the class and specifies its value there. The need for the out of class definition is explained in this link.


Now, let us see the problem with templates.

template< class T >
struct problem
{
    static const int a = 5;
    const int b;

    problem( int b_ ) : b( std::max( b_, a ) )
    {}
};

template< class T >
const int problem< T >::a;

int main()
{
    problem< char > d_Bad( 666 );
    return 0;
}

When compiling with /Za, the linker throws an error "LNK2019: unresolved external symbol". That error does not appear with option /Ze.The major problem is that some boost libraries use BOOST_STATIC_CONSTANT and BOOST_NO_INCLASS_MEMBER_INITIALIZATION in code similar to the above snipet.


Hacking some:

template< class T >
struct fixed
{
    static const int a;
    const int b;

    fixed( int b_ ) : b( std::max( b_, a ) )
    {}
};

template< class T >
const int fixed< T >::a = 5;

int main()
{
    fixed< char > d_Good( 777 );
    return 0;
}

This code now compiles with /Za.

Questions:

1) What does the C++11 standard say about templates and static integral constants? Can/must they have an out of class definition but their value be provided in the class definition?

2) Does boost have some workarounds?


UPDATE

It is important to keep the std::max in the code because (I think) it tries to get the reference to its parameters. If one uses b_<a then the compiler simply optimizes those constants away.

like image 470
Hector Avatar asked Jan 07 '15 23:01

Hector


2 Answers

First of all, a declaration of a static data member in class is never a definition. If you odr-use that variable, a definition must be present - out of class, of course.

std::max does indeed odr-use a, as its parameters are references, and variables are odr-used if a reference is bound to them ([basic.def.odr]/3). (That is indeed a problem with max - it shouldn't odr-use a, really.)
In @sehe's answer, he is using the ternary operator directly, avoiding an odr-use as the lvalue-to-rvalue transformation is immediately applied and yields a constant expression.

  1. It's quite simple. When the definition of a static data member of a class template is needed, i.e. when that member is odr-used as in your case, the (namespace scope) definition is instantiated. [temp.inst]/2:

    Unless a member of a class template or a member template has been explicitly instantiated or explicitly specialized, the specialization of the member is implicitly instantiated when the specialization is referenced in a context that requires the member definition to exist; in particular, the initialization (and any associated side-effects) of a static data member does not occur unless the static data member is itself used in a way that requires the definition of the static data member to exist.

    And the definition is done exactly as you did it. [temp.static]/1:

    A definition for a static data member or static data member template may be provided in a namespace scope enclosing the definition of the static member’s class template.

    [ Example:

    template<class T> class X {
        static T s;
    };
    template<class T> T X<T>::s = 0;
    

    The initializer can be supplied at the declaration in-class when the member is of const integral type, but that doesn't affect the semantics of the ODR in this respect. The definition is still required in the same way and written just as you did it.

Hence it seems what you see is solely a VC++ bug.

like image 184
Columbo Avatar answered Nov 09 '22 15:11

Columbo


A workaround that I used for a long time and recently became more useful in c++11:

Live On Coliru

struct easy
{
    enum : int { a = 5 };
    int b;

    constexpr easy(int b_) : b(b_<a? a : b_)
    {}
};

It became more useful because you can now specify the underlying type:

struct Container
{
    enum special_position : size_t { npos = size_t(-1), at_bof = 0 };
};

Of course it's limited to (userdefined/primitive) integral types.


Externally defined constants may have the benefit that they could actually be changed by only recompiling the translation unit that defines the value.

like image 41
sehe Avatar answered Nov 09 '22 14:11

sehe