我想定义可用于任意类型的自定义常量(例如float
,double
等)。例如,假设我希望定义一个值为pi的常量。
显而易见的解决方案是使用#define pi 3.14159265359
,但是pi
不会在命名空间中,我冒着名称冲突的风险。我没有使用C ++ 14,所以我不能使用variable template。我能想到这样做的最好方法如下:
#include <iostream>
using namespace std;
namespace constants {
template<typename T> T pi() {
return 3.14159265359;
}
}
int main() {
float pitest = 0;
pitest = constants::pi<float>();
cout << pitest << endl;
cout << constants::pi<long double>() << endl;
cout << constants::pi<int>() << endl;
return 0;
}
我现在可以在命名空间中定义这些常量,我可以根据需要使用任意(数字)类型。但是,至少有两个不良特征:
pitest = constants::pi<float>();
而不是简单的pitest = constants::pi();
,即使pitest
显然是float
。有更好的方法吗?
答案 0 :(得分:6)
为什么不使用自动转换为任何类型的特殊对象?
static struct {
template<class T> operator T() const constexpr
{ return (T)3.14159265359; }
} pi;
您甚至可以为更大的类型,任意精度算术,公式系统等添加特化。
答案 1 :(得分:2)
static struct { template<class T> operator T() const constexpr { return 3.14; } } pi;
是第一步。
template<class T> struct type {};
template<class T> constexpr T get_pi( type<T> ) { return 3.14; }
static struct { template<class T> operator T() const constexpr { return get_pi( type<T>{} ); } } pi;
是第二种类型 - 现在您可以为新类型添加新的重载而无需专门化。所有pi
所做的就是进行魔法铸造。
可悲的是,这要求我们确切地匹配类型 - int
的新重载不会解决long
,或者double
的新重载会赢得&#39}。解决float
。
但这是C ++,我们可以做到!
template<class T> struct contra_type {
constexpr contra_type(contra_type&&) {};
template<class U, class=typename std::enable_if< std::is_convertible< T, U >::value >::type>
constexpr contra_type( contra_type<U>&& ) {}
};
template<class T> constexpr auto get_pi( type<T>, ... )->decltype(T(3.14)) { return T(3.14); }
static struct { template<class T> operator T() const constexpr { return get_pi( contra_type<T>{} ); } } pi;
是下一步。现在我们可以为get_pi( type<bignum> )
等添加重载并让它工作。事实上,任何可以从bignum
隐式转换的内容都会自动调用get_pi( type<bignum> )
。
不确定如何启用ADL - 如果我使用T*
,我将获得协变过载而不是逆变重载(并且因为我们实际上在返回类型上超载,所以不需要我想要。)
contra_type<U>
可转换为contra_type<T>
时, T
才能转换为U
。这意味着pi_func( contra_type<Foo>{} )
会尝试找到一个pi_func
,其类型可以转换为Foo
,然后再调用它。
...
重载为我们提供了一个完全匹配所有内容的默认实现,但因为它有...
,所以最好调用任何其他函数而不是匹配它。