C ++模板静态整数常量:出类定义

时间:2015-01-07 23:51:04

标签: templates c++11 boost initialization static-members

这个问题是关于使用flag / Za的Visual Studio C ++ 2013中模板和静态积分常量之间的关系。它对升级库有影响。


首先,让我们检查没有模板的代码:

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;
}

根据compiler option /Za的手册页:“在标准(/ Za)下,您必须为数据成员制定一个类外定义”。该页面中的示例和上面的代码声明了类中的静态常量,并在那里指定了它的值。在this link中解释了对课外定义的需求。


现在,让我们看一下模板的问题。

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;
}

使用/ Za进行编译时,链接器会抛出错误“LNK2019:未解析的外部符号”。选项/Ze不会出现该错误。主要问题是某些boost库在类似于上述snipet的代码中使用BOOST_STATIC_CONSTANT和BOOST_NO_INCLASS_MEMBER_INITIALIZATION。


黑客攻击:

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;
}

此代码现在使用/ Za编译。

问题:

1)C ++ 11标准对模板和静态积分常数有何看法?可以/必须具有类外定义,但它们的值是否在类定义中提供?

2)boost有一些解决方法吗?


更新

std::max保留在代码中非常重要,因为(我认为)它会尝试获取对其参数的引用。如果使用b_<a,则编译器只会优化这些常量。

2 个答案:

答案 0 :(得分:5)

首先,类中静态数据成员的声明永远不是定义。 如果你使用那个变量,必须有一个定义 - 当然是在课外。

std::max确实使用a,因为它的参数是引用,如果引用绑定到它们([basic.def.odr] / 3),则变量使用odr。 (这确实是max的一个问题 - 它确实不应该使用a。) 在@ sehe的回答中,他直接使用三元运算符,避免了使用odr的问题,因为lvalue-to-rvalue转换会立即应用并产生一个常量表达式。

  1. 这很简单。当需要定义类模板的静态数据成员时,即当该成员在您的情况下使用时,将实例化(命名空间作用域)定义。 [temp.inst] / 2:

      

    除非是类模板或成员模板的成员   显式实例化或明确专门化,专业化   当特化是隐式实例化成员的时候   在需要成员定义存在的上下文中引用;   特别是,初始化(以及任何相关的副作用)   除非静态数据成员,否则不会发生静态数据成员   本身的使用方式需要静态的定义   数据成员存在。

    定义完全按照你的方式完成。 [temp.static] / 1:

      

    静态数据成员或静态数据成员模板的定义   可以在包含该定义的命名空间范围内提供   静态成员的类模板。

         

    [示例:

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

    当成员为const整数类型时,可以在类中的声明中提供初始化程序,但这不会影响ODR在这方面的语义。仍然需要以相同的方式定义该定义,并按照您的方式编写。

  2. 因此,您看到的只是一个VC ++错误。

答案 1 :(得分:2)

我使用了很长时间的解决方法,最近在c ++ 11中变得更有用:

<强> Live On Coliru

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

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

它变得更有用,因为您现在可以指定基础类型:

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

当然它仅限于(userdefined / primitive)整数类型。


  

外部定义的常量可能具有以下优点:只需重新编译定义值的转换单元即可实际更改它们。