没有C ++ 14定义模板常量的正确方法?

时间:2014-09-19 19:06:43

标签: c++ templates namespaces const constants

我想定义可用于任意类型的自定义常量(例如floatdouble等)。例如,假设我希望定义一个值为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;
}

我现在可以在命名空间中定义这些常量,我可以根据需要使用任意(数字)类型。但是,至少有两个不良特征:

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

2 个答案:

答案 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,然后再调用它。

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