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

标签 c++ templates c++11 cuda constexpr

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

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

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 的类型具有“特化” :

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++-5 检查过它, g++-6clang-3.8编译器使用 -std=c++14旗帜。他们都提示

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

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

我问题的最后一部分涉及 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() );

是在第一种情况下,您将引用直接绑定(bind)到 ONE,而在其他情况下则不是。更具体地说,您在第一种情况下odr-usingONE,但在其他两种情况下不是。当您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;

关于c++ - 编译时的模板和 constexpr 推导取决于编译器和优化标志,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39642434/

相关文章:

c++ - 这个 for 循环语句的正确语法

c++ - 构造临时对象是左值?

c++ - 不能使用基类模板成员函数

c++ - 为什么 C++ 模板参数应该声明为类类型?

C++检测类型是否具有模板参数

c++ - 功能模板的部分属性在 GCC 中被静默忽略

c++ - 定时器qt c++输出的非线性结果

c++ - 得到很多重定义错误

c++ - 为什么编译器无法用文字确定 std::max 的模板?

c++ - 返回类型足够大以保存结果的 Sum 函数