2014-09-19 32 views
0

我想定义可以用于任意类型的自定义常量(例如floatdouble等)。作为一个例子,假设我想定义一个常量,其值为pi。没有C++ 14的定义模板常量的正确方法?

明显的解决方案是使用#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; 
} 

我现在可以在命名空间中定义这些常量,我可以使用任意(数值)为需要的类型。然而,这至少有两个不希望的特征:

  1. 它需要一个不应该是必要的函数调用(它只是一个常量!)。
  2. 即使函数正在返回已知类型的变量,我也必须在函数调用中指定类型。例如,在上面的代码中,我必须使用pitest = constants::pi<float>();而不是简单的pitest = constants::pi();,即使pitest显然是float

有没有更好的方法来做到这一点?

回答

6

为什么不使用自动转换为任何类型的特殊对象?

static struct { 
    template<class T> operator T() const constexpr 
    { return (T)3.14159265359; } 
} pi; 

你甚至可以为更大的类型,任意精度算术,公式系统等等添加特化。

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新的过载不会解决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,然后调用该类型。

...重载为我们提供了一个完全匹配所有内容的默认实现,但因为它有...,所以最好调用任何其他函数而不是匹配它。

+0

对于我打算使用这些常量而言,这有点矫枉过正,但对于超出范围而言是+1。 – Null 2014-09-19 19:46:55

+1

@Null没有像过度工程这样的杀戮。 – Yakk 2014-09-19 19:55:18