【问题标题】:Template Function Specialization for Integer Types整数类型的模板函数特化
【发布时间】:2012-08-22 12:57:15
【问题描述】:

假设我有一个模板函数:

template<typename T>
void f(T t)
{
    ...
}

我想为所有原始整数类型编写一个特化。最好的方法是什么?

我的意思是:

template<typename I where is_integral<I>::value is true>
void f(I i)
{
    ...
}

编译器为整数类型选择第二个版本,为其他所有类型选择第一个版本?

【问题讨论】:

  • C++11 中的函数有模板特化吗?我认为在 C++03 中它只是重载。
  • 我一直在寻找类似的东西,但失败了。我所能做的就是定义一个普通的模板,然后在里面检查给定的参数是否是整数类型
  • @AlexanderChertov 在 C++03 中有,但 it is complicated。这同样适用于 C++11。
  • @AlexanderChertov:是的,即使在 C++03 中,函数模板也可以特化,但不能部分特化。

标签: c++ templates


【解决方案1】:

使用SFINAE

// For all types except integral types:
template<typename T>
typename std::enable_if<!std::is_integral<T>::value>::type f(T t)
{
    // ...
}

// For integral types only:
template<typename T>
typename std::enable_if<std::is_integral<T>::value>::type f(T t)
{
    // ...
}

请注意,即使是声明,您也必须包含完整的 std::enable_if 返回值。

C++17 更新:

// For all types except integral types:
template<typename T>
std::enable_if_t<!std::is_integral_v<T>> f(T t)
{
    // ...
}

// For integral types only:
template<typename T>
std::enable_if_t<std::is_integral_v<T>> f(T t)
{
    // ...
}

【讨论】:

  • 您已将函数的返回值从 void 更改为其他值。如果原始函数返回一个类型怎么办?例如 X 类? template&lt;class T&gt; X f(T t) ?那时它会是什么样子?
  • @AndrewTomazos-Fathomling, std::ebable_if::type 无效,如果只给出 1 个模板参数;或第二个模板参数。
  • @AndrewTomazos-Fathomling std::enable_if 的第二个模板参数默认为void。那是你的返回类型。我将其保留为默认值,但您可以将其更改为您想要的任何返回类型。您甚至可以让整数类型返回与非整数类型不同的类型。
  • 哦,原来是template&lt;class T&gt; typename enable_if&lt;is_integral&lt;T&gt;::value, X&gt;::type f(T t)
  • 可以使用std::enable_ifa bit more aesthetically pleasing,至少在我看来。
【解决方案2】:

我会使用重载决议。这使您不必使用粗俗的 SFINAE hack。不幸的是,有很多领域是您无法避免的,但幸运的是,这不是其中之一。

template<typename T>
void f(T t)
{
  f(t, std::is_integral<T>());
}

template<typename T>
void f(T t, std::true_type)
{ 
  // ...
}

template<typename T>
void f(T t, std::false_type)
{ 
  // ...
}

【讨论】:

    【解决方案3】:

    使用 c++11,可以使用 std::enable_if (http://en.cppreference.com/w/cpp/types/enable_if) 来做到这一点:

    template<typename T, class = typename std::enable_if<std::is_integral<T>::value>::type>
    void f(T t) {...}
    

    【讨论】:

    • 这仍然不起作用,因为您将获得 2 个整数类型的重载,编译器将无法选择一个。
    • 我不喜欢这种方法的地方在于,您实际上可以将类型传递给第二个模板参数并弄乱 SFINAE(与函数的默认第二个参数相同的问题)。通过返回类型来做到这一点是万无一失的。另一方面,对于外行来说,不清楚该函数返回什么。
    【解决方案4】:

    您可以使用可以像这样专门化的帮助模板:

    #include <string>
    #include <iostream>
    #include <type_traits>
    
    template <typename T, bool = std::is_integral<T>::value>
    struct Foo {
            static void bar(const T& t) { std::cout << "generic: " << t << "\n"; }
    };
    template <typename T>
    struct Foo<T, true> {
            static void bar(const T& t) { std::cout << "integral: " << t << "\n"; }
    };
    
    template <typename T>
    static void bar(const T& t) {
            return Foo<T>::bar(t);
    }
    
    int main() {
            std::string s = "string";
            bar(s);
            int i = 42;
            bar(i);
            return 0;
    }
    

    输出:

    generic: string
    integral: 42
    

    【讨论】:

    • +1 用于类模板委托,因为这不仅允许显式特化,还允许部分特化。这也符合 Sutter&Alexandrescu 编码标准的第 66 条:“不要专门化函数模板。”
    • @rhalbersma 我喜欢这个答案,但我不认为你的推理是合理的。这里没有答案使用函数模板专业化。使用函数模板专门化来解决这个问题根本不是一个实际的选择。
    • 问题本身要求“专业化”。
    • @Dave 尽管您的回答在技术上是正确的,但 SFINAE 是一个被过度使用且具有潜在危险的工具。函数模板特化和 SFINAE 都依赖于难以调试的函数重载决议效果。通常更容易使用标签调度(例如,用于多个替代方案和非模板函数)或委托给类专业化(也有一种形式的部分专业化)。
    • 是的,我同意。我对这个答案投了赞成票,我实际上认为它比我自己的要好。但我有不同的理由。我喜欢它,因为对于函数的用户来说,签名看起来是正确的(没有疯狂的返回类型,他们不确定实际类型是什么)
    【解决方案5】:

    这里是 C++20 解决方案

    template<std::integral T>
    void f(T v) {
       ...
    }
    

    我认为它实际上是重载的函数,但工作方式相同。

    【讨论】:

      【解决方案6】:

      仅通过在函数体内实现不同版本来实现更直接、更易读的方式怎么样?

          template<typename T>
          void DoSomething(T inVal) {
              static_assert(std::is_floating_point<T>::value || std::is_integral<T>::value, "Only defined for float or integral types");
              if constexpr(std::is_floating_point<T>::value) {
                  // Do something with a float
              } else if constexpr(std::is_integral<T>::value) {
                  // Do something with an integral
              }
          }
      

      您不必担心性能。条件是编译时间常数,下降编译器会将它们优化掉。 不幸的是,“if constexpr”是 c++17,但是当两个版本都编译且两种类型都没有错误时,您可以删除“constexpr”

      【讨论】:

      • 这要么要求:// Do something with a float 编译为非浮点 T,// Do something with an integral 编译为非整数 T;或 if constexpr,在 2012 年提出问题时并不存在
      • 你的意思是早在 2012 年,即使编译器在编译时已经丢弃了错误的版本,编译器也会一直尝试为任何类型编译这两个版本?
      • “两个版本”?是的,你有一个 if 语句。两个分支都必须是合法的 C++ 语句。这正是 if constexpr 的创建目的
      • 问题是关于c++11,没有if constexpr
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多