【问题标题】:C++ template static integer constants: out of class definitionC ++模板静态整数常量:超出类定义
【发布时间】:2015-03-06 00:21:29
【问题描述】:

这个问题是关于带有标志 /Za 的 Visual Studio C++ 2013 中模板和静态整数常量之间的关系。它对 boost 库有影响。


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

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 库在类似于上述代码段的代码中使用 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_&lt;a,那么编译器会简单地优化这些常量。

【问题讨论】:

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


【解决方案1】:

首先,类中静态数据成员的声明绝不是定义。 如果您使用该变量,则必须存在定义 - 当然,不在类中。

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

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

    除非类模板的成员或成员模板已被 显式实例化或显式特化,特化 当特化时,成员的隐式实例化 在需要成员定义存在的上下文中引用; 特别是初始化(以及任何相关的副作用) 除非静态数据成员 本身以需要定义静态的方式使用 数据成员存在。

    并且定义完全按照您所做的那样完成。 [temp.static]/1:

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

    [ 例子:

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

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

因此,您所看到的似乎只是一个 VC++ 错误。

【讨论】:

  • "std::max 确实 odr-use a,因为它的参数是引用" 你确定这足以要求定义吗? coliru.stacked-crooked.com/a/1d0913fc7bc14b31
  • 啊,看来任何函数调用表达式的潜在结果集都是空的;因此它是 ODR 使用的。正确的? (按值获取的max 函数不需要这个,因为有一个子表达式将 l-t-r 转换应用于ex。)
  • reported它是一个错误。
  • @dyp 我的论证更简单:在调用 f(x) 时 l-t-r 转换未应用于 x,请考虑 id 表达式 x。假设ex 是表达式x。表达式e 也是x - 并且e 不满足[basic.def.odr]/3 第一句最后一部分的要求 - 既没有应用ltr 转换,也没有应用它是一个未评估的操作数。这看起来对吗?
  • @dyp 是的,我就是这个意思。 ex 是 e 的潜在结果的元素的唯一表达式 e 是 x 本身。
【解决方案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 };
};

当然它仅限于(用户定义/原始)整数类型。


外部定义的常量的好处是,它们实际上可以通过只重新编译定义值的翻译单元来更改。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2016-03-05
  • 1970-01-01
  • 1970-01-01
  • 2013-07-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多