【问题标题】:Proper way to define templated constant without C++14?在没有 C++14 的情况下定义模板化常量的正确方法?
【发布时间】:2014-09-19 19:06:43
【问题描述】:

我想定义可用于任意类型的自定义常量(例如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&lt;float&gt;(); 而不是简单的pitest = constants::pi();,即使pitest 显然是float

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

【问题讨论】:

    标签: c++ templates namespaces constants


    【解决方案1】:

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

    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&lt;bignum&gt; ) 等添加重载并让它工作。事实上,任何可以从bignum 隐式转换的东西都会自动调用get_pi( type&lt;bignum&gt; )

      不确定如何启用 ADL - 如果我使用 T*,我将得到协变重载而不是逆变重载(而且我们实际上是在返回类型上重载,这不是我想要的)。

      contra_type&lt;U&gt; 可转换为 contra_type&lt;T&gt; 当且仅当 T 可转换为 U。这意味着pi_func( contra_type&lt;Foo&gt;{} ) 将尝试找到一个pi_func,它采用可以转换为Foo 的类型,然后调用它。

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

      【讨论】:

      • 这对于我对这些常量的预期用途来说有点矫枉过正,但 +1 表示超越。
      • @Null 没有像过度工程这样的杀戮。
      猜你喜欢
      • 2011-02-23
      • 1970-01-01
      • 2015-08-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-11
      • 2015-08-10
      • 1970-01-01
      • 2011-03-23
      相关资源
      最近更新 更多