9

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.

Community
  • 1
  • 1
Hector
  • 2,464
  • 3
  • 19
  • 34
  • _"If one uses b_ – sehe Jan 08 '15 at 00:21
  • Perhaps the trailing paragraph of my answer is essential to your real question? If so, obviously you **always** need to separate the static member initialization into a separate translation unit. – sehe Jan 08 '15 at 00:22
  • @sehe: it is relevant because I cannot compile boost::pool with /Za in Visual Studio C++13. I traced the culprit to the behavior in the question. After I find out who is responsible, I will report the bug either to the boost people or to the MS fellas. – Hector Jan 08 '15 at 00:25
  • I've posted a near exhaustive comparison of [{max,ternary}/{enum,static-field-in-class,static-field-out-of-class} in the comment at my answer](http://stackoverflow.com/questions/27830761/c-template-static-integer-constants-out-of-class-definition/27830856?noredirect=1#comment44068253_27830856). TL;DR I was right: it doesn't matter. The compiler inlines the `std::max` variant just the same (as it should) – sehe Jan 08 '15 at 00:43
  • @sehe: Thank you. What you did proves that the compilers in that page do not show an error. But then, those compiles have no problem with: #include struct A { static const int a = 5; A(){ std::cout << & a;} }; int main() { A d_A; return 0; } – Hector Jan 08 '15 at 01:46
  • yes they do: [clang++](http://coliru.stacked-crooked.com/a/84d132808bd2f975) and [gcc](http://coliru.stacked-crooked.com/a/b5360c3562ae7a19). In fact, I proved that your `std::max` is not essential and in facts generates the same code. I'll state it **[again](http://stackoverflow.com/questions/27830761/?noredirect=1#comment44067954_27830761)**: "_Perhaps the trailing paragraph of my answer is essential to your real question? If so, obviously you always need to separate the static member initialization into a separate translation unit_" – sehe Jan 08 '15 at 01:57
  • FWIW. I started to realize the problem you point at related to templates. Lemme think. (I have no MSVC available sadly) – sehe Jan 08 '15 at 02:00
  • @sehe: You seem to use a better online compiler that [this one](http://goo.gl/l5L8MV). – Hector Jan 08 '15 at 02:03
  • You seem to be confusing compilation and linking :) Mmm. That's my bad, then. I forgot to link the sample while editing on Godbolt directly. So now the problem begins to emerge from the fog: it's actually **just** about linkage of the static member. – sehe Jan 08 '15 at 02:07
  • I can only +1 the question. Will bounty it if nothing comes of it. And you can always plead at the Boost mailing lists. MSVC compatibility is a great thing. (I'm not acquainted with /Za though, so don't know how crucial that would be) – sehe Jan 08 '15 at 02:10

2 Answers2

6

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.

Columbo
  • 60,038
  • 8
  • 155
  • 203
  • *"std::max does indeed odr-use a, as its parameters are references"* Are you sure that is sufficient to require a definition? http://coliru.stacked-crooked.com/a/1d0913fc7bc14b31 – dyp Jan 11 '15 at 03:07
  • Ah, it seems the *set of potential results* of any function call expression is empty; therefore it is ODR-used. Right? (A `max` function that takes by value would not require this, since there's a sub-expression that applies the l-t-r conversion to `ex`.) – dyp Jan 11 '15 at 03:19
  • I [reported](https://connect.microsoft.com/VisualStudio/feedback/details/1079894) it as a bug. – Hector Jan 11 '15 at 09:53
  • @dyp My argumentation is easier: In a call `f(x)` where the l-t-r conversion is not applied to `x`, consider the id-expression `x`. Let's say `ex` is the expression `x`. The expression `e` is then also `x` - and `e` does not meet the requirement in the very last part of the first sentence of [basic.def.odr]/3 - neither is the l-t-r conversion applied, nor is it an unevaluated operand. Does that seem about right? – Columbo Jan 11 '15 at 10:57
  • @Columbo: I googled odr-use and I found [this](http://en.wikipedia.org/wiki/One_Definition_Rule). For l-t-r conversion, I get "Liters conversion calculators" ;-) .... what does l-t-r mean in this context? – Hector Jan 11 '15 at 11:53
  • @Hector lvalue-to-rvalue conversion. – Columbo Jan 11 '15 at 11:56
  • Since the wording is "whose name appears **as** a potentially-evaluated expression `ex`", I think `ex` *must be* the id-expression `x`. As far as I understand the requirements on `e`, there must be an expression `e` that `ex` is part of, where l-t-r is applied to `e` or the value of `e` is discarded. So I interpret that as "it is sufficient if such an expression exists", not as "no other such expression must exist". – dyp Jan 11 '15 at 15:30
  • 1
    @dyp Yeah, that's what I meant. The only expression e for which ex is an element of the potential results of e is `x` itself. – Columbo Jan 11 '15 at 15:34
2

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.

sehe
  • 374,641
  • 47
  • 450
  • 633
  • don't you need `enum class` if you want to specify the underlying type? – vsoftco Jan 08 '15 at 00:06
  • 1
    @vsoftco no (at least not on my compilers) – sehe Jan 08 '15 at 00:07
  • @vsoftco: The `class` part makes it strongly typed. Without it, the declarations spill out to the container structure. – Hector Jan 08 '15 at 00:08
  • `warning: scoped enums only available with -std=c++11 or -std=gnu++11` if you compile with `g++ -pedantic` – vsoftco Jan 08 '15 at 00:08
  • @Hector that's not what strongly-typed means. Namespace pollution doesn't mean "not strongly typed". – sehe Jan 08 '15 at 00:09
  • @sehe: Boost has something similar baked in their BOOST_STATIC_CONSTANT macro. The problem arises when it is used with templates. – Hector Jan 08 '15 at 00:09
  • @Hector There is no problem using this in templates. Maybe you could exposition the problem with this in your question? – sehe Jan 08 '15 at 00:10
  • @sehe, my comment was a bit off, what I wanted to say is that this is a C++11 feature, you can use just `enum` but have to specify `-std=c++11` – vsoftco Jan 08 '15 at 00:11
  • @sehe: Am I misreading [this](http://stackoverflow.com/questions/12581064/enum-vs-strongly-typed-enum) and [this](http://www.codeguru.com/cpp/cpp/article.php/c19083/C-2011-Stronglytyped-Enums.htm)? – Hector Jan 08 '15 at 00:12
  • @sehe: the problem is that the 3rd snipet does not compile with /Za and I thought it should. – Hector Jan 08 '15 at 00:14
  • @Hector I'm not suggesting `enum class` here because it's irrelevant to the cause. In fact, you'd probably _not_ want the strongly typed variant here because you want to use that `npos` as a `size_t` indeed. The thing that c++11 adds to non-class enums is _underlying types_. (Only integral, of course, will add that to my answer) – sehe Jan 08 '15 at 00:15
  • @vsoftco I said that at the very start. Of course, in the `int` case you can just use it in c++03 by dropping `:int` – sehe Jan 08 '15 at 00:18
  • 1
    I think it should be irrelevant: **[max](http://goo.gl/vuY1Cy)** vs. **[ternary](http://goo.gl/u6U72r)** vs. **[ternary, static member](http://goo.gl/pWcfb0)** vs. **[max, static member](http://goo.gl/q4Zrf6)** vs. **[max, static member, out-of-class](http://goo.gl/A3YcZz)** vs. **[ternary, static member, out-of-class](http://goo.gl/hA2vkK)** . Literally the only difference in generated code will be the presence/absense of `easy::a` in the data segment. – sehe Jan 08 '15 at 00:40
  • @Hector So, in short, this workaround _(you asked for workarounds :))_ is nice, but it doesn't help you because patching all relevant spots in Boost is impractical? Or is there anything else about this workaround that makes it unusable? – sehe Jan 08 '15 at 02:11
  • @sehe: I only need to patch boost::pool (which I need to use). I thought there might be a workaround native to boost, something similar to macros like BOOST_NO_INCLASS_MEMBER_INITIALIZATION. I asked the question here because I would like to know the answer according to the standards (something like [this](http://stackoverflow.com/a/24682473/2549876)). The problem seems to be that the static constant is not defined yet when std::max uses it. – Hector Jan 08 '15 at 02:24