编译时的模板和 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++-5
、g++-6
和使用 -std=c++14
标志的 clang-3.8
编译器对其进行了检查。他们都抱怨
undefined reference to `Tester<int>::ONE'
尽管据我所知,所有使用的功能都在标准中,因此应该得到支持。有趣的是,只要我添加优化标志 O1
、O2
或 O3
,编译就会成功。所以我的第二个问题是:如果优化标志处于活动状态,是否有编译器只进行编译时计算的策略?我本以为至少声明为编译时间常量的东西是 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;
下面的问题是从一个更大的代码中浓缩出来的。因此有些表达式看似矫枉过正或不必要,但对原始代码至关重要。
考虑拥有一个包含编译时常量和简单容器的结构 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
:
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++-6
和使用 -std=c++14
标志的 clang-3.8
编译器对其进行了检查。他们都抱怨
undefined reference to `Tester<int>::ONE'
尽管据我所知,所有使用的功能都在标准中,因此应该得到支持。有趣的是,只要我添加优化标志 O1
、O2
或 O3
,编译就会成功。所以我的第二个问题是:如果优化标志处于活动状态,是否有编译器只进行编译时计算的策略?我本以为至少声明为编译时间常量的东西是 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;