编译时的模板和 constexpr 推导取决于编译器和优化标志

Template and constexpr deduction at compiletime dependent on compiler and optimization flags

下面的问题是从一个更大的代码中浓缩出来的。因此有些表达式看似矫枉过正或不必要,但对原始代码至关重要。

考虑拥有一个包含编译时常量和简单容器的结构 class:

template<typename T> struct CONST
{
    static constexpr T ONE()
    {
         return static_cast<T>( 1 );
    }
};

template<typename T> class Container
{
public:
    using value_type = T;
    T value;
};

现在有一个模板函数,对于提供 value_type:

的类型有一个 "specialization"
template<typename T> void doSomething( const typename T::value_type& rhs )
{}

现在我希望这应该有效:

template<typename T> class Tester
{
public:
    static constexpr T ONE = CONST<T>::ONE();

    void test()
    {
        doSomething<Container<T>>( ONE );
    }
};

有趣的一点是,编译器不会抱怨 Tester<T>::ONE 的定义,而是它的用法。此外,如果我在函数调用中使用 CONST<T>::ONE() 甚至 static_cast<T>( ONE ) 而不是 ONE,它也不会抱怨。但是,两者都应该在编译时已知,因此可用。 所以我的第一个问题是:编译器在工作的情况下是否甚至在编译时进行计算?

我使用 g++-5g++-6 和使用 -std=c++14 标志的 clang-3.8 编译器对其进行了检查。他们都抱怨

undefined reference to `Tester<int>::ONE'

尽管据我所知,所有使用的功能都在标准中,因此应该得到支持。有趣的是,只要我添加优化标志 O1O2O3,编译就会成功。所以我的第二个问题是:如果优化标志处于活动状态,是否有编译器只进行编译时计算的策略?我本以为至少声明为编译时间常量的东西是 always 推导出来的!

我问题的最后一部分涉及 NVIDIA nvcc 编译器(8.0 版)。由于我只能传-std=c++11给它,可能有些功能一般没有覆盖到。但是,使用上面的主机编译器之一,它会抱怨

error: identifier "Tester<int> ::ONE" is undefined in device code

即使通过了优化标志!这显然与上面的问题完全相同,但是虽然上面的问题更具学术性(因为我可以简单地使用优化标志来摆脱问题),但这里确实是一个问题(关于我不知道的事实,当我使用上面提到的解决方法时在编译时做了什么——这也更丑陋)。所以我的第三个问题是:有没有办法在设备代码中也使用优化?

以下代码是纯主机和 nvcc 编译器的 MWE:

#include <iostream>
#include <cstdlib>

#ifdef __CUDACC__
    #define HD __host__ __device__
#else
    #define HD
#endif


template<typename T> struct CONST
{
    HD static constexpr T ONE()
    {
        return static_cast<T>( 1 );
    }
};


template<typename T> class Container
{
public:
    using value_type = T;
    T value;
};


template<typename T> HD void doSomething( const typename T::value_type& rhs ) {}


template<typename T> class Tester
{
public:
    static constexpr T ONE = CONST<T>::ONE();

    HD void test()
    {
        doSomething<Container<T>>( ONE );
        // doSomething<Container<T>>( static_cast<T>( ONE ) );
        // doSomething<Container<T>>( CONST<T>::ONE() );
    }
};


int main()
{
    using t = int;

    Tester<t> tester;
    tester.test();

    return EXIT_SUCCESS;
}

提前致谢!

这两者的区别:

doSomething<Container<T>>( ONE );

相对于这两个:

doSomething<Container<T>>( static_cast<T>( ONE ) );
doSomething<Container<T>>( CONST<T>::ONE() );

是在第一种情况下您将引用直接绑定到 ONE 而其他情况则不是。更具体地说,您在第一种情况下使用 odr-using ONE,但在其他两种情况下则不是。当您 odr-use 一个实体时,它需要一个定义,并且 ONE 当前已声明但未定义。

您需要定义它:

template<typename T>
class Tester
{
public:
    // declaration
    static constexpr T ONE = CONST<T>::ONE();
    // ..
};

// definition
template <typename T>
constexpr T Tester<T>::ONE;