C++函数模板部分特化?

Nar*_*rek 72 c++ templates partial-specialization template-specialization

我知道下面的代码是类的部分特化:

template <typename T1, typename T2> 
class MyClass { 
  … 
}; 


// partial specialization: both template parameters have same type 
template <typename T> 
class MyClass<T,T> { 
  … 
}; 
Run Code Online (Sandbox Code Playgroud)

另外我知道C++不允许函数模板部分特化(只允许完整).但是我的代码是否意味着我对一个/同一类型的参数有部分专业的函数模板?因为它适用于Microsoft Visual Studio 2010 Express!如果不是,那么请您解释部分专业化概念吗?

#include <iostream>
using std::cin;
using std::cout;
using std::endl;

template <typename T1, typename T2> 
inline T1 max (T1 const& a, T2 const& b) 
{ 
    return a < b ? b : a; 
} 

template <typename T> 
inline T const& max (T const& a, T const& b)
{
    return 10;
}


int main ()
{
    cout << max(4,4.2) << endl;;
    cout << max(5,5) << endl;
    int z;
    cin>>z;
}
Run Code Online (Sandbox Code Playgroud)

iam*_*ind 70

在该示例中,您实际上正在重载(不专门化)max<T1,T2>函数.偏专业化语法应该看起来有点像下面(要是让):

//Partial specialization is not allowed by the spec, though!
template <typename T> 
inline T const& max<T,T> (T const& a, T const& b)
{                  ^^^^^ <--- specializing here
    return 10;
}
Run Code Online (Sandbox Code Playgroud)

[注:在一个功能模板的情况下,只有专业化由C++标准(不包括编译器扩展)允许的.]

  • 任何能够解释_why_部分专业化的人都是不允许的? (11认同)
  • @NHDaly,它不会给出歧义错误,因为 1 个函数比另一个更匹配。为什么它为`(int, int)`选择`(T, T)`而不是`(T1, T2)`,是因为前者保证了有2个参数并且两者类型相同;后者只保证有2个参数。编译器总是选择准确的描述。例如,如果您必须在“河流”的 2 种描述之间做出选择,您会选择哪一种?“收集水”与“收集流动的水”。 (2认同)
  • @kfsone,我认为此功能正在审核中,因此可以进行解释。你可以参考[this open-std section](/sf/ask/2845688611/),我在[为什么C++标准不允许函数模板部分特化?](https://stackoverflow .com/q/40652694/514235) (2认同)

Rub*_*ens 37

由于不允许部分特化 - 正如其他答案所指出的那样 - 你可以使用std::is_same和解决它std::enable_if,如下所示:

template <typename T, class F>
inline typename std::enable_if<std::is_same<T, int>::value, void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with ints! " << f << std::endl;
}

template <typename T, class F>
inline typename std::enable_if<std::is_same<T, float>::value, void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with floats! " << f << std::endl;
}

int main(int argc, char *argv[]) {
    typed_foo<int>("works");
    typed_foo<float>(2);
}
Run Code Online (Sandbox Code Playgroud)

输出:

$ ./a.out 
>>> messing with ints! works
>>> messing with floats! 2
Run Code Online (Sandbox Code Playgroud)

编辑:如果您需要能够处理剩下的所有其他案例,您可以添加一个定义,说明已处理的案例不应该匹配 - 否则您将陷入模棱两可的定义.定义可以是:

template <typename T, class F>
inline typename std::enable_if<(not std::is_same<T, int>::value)
    and (not std::is_same<T, float>::value), void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with unknown stuff! " << f << std::endl;
}

int main(int argc, char *argv[]) {
    typed_foo<int>("works");
    typed_foo<float>(2);
    typed_foo<std::string>("either");
}
Run Code Online (Sandbox Code Playgroud)

哪个产生:

$ ./a.out 
>>> messing with ints! works
>>> messing with floats! 2
>>> messing with unknown stuff! either
Run Code Online (Sandbox Code Playgroud)

虽然这种全案例的事情看起来有点无聊,因为你必须告诉编译器你已经完成的所有事情,最多可以处理5个或更多的特化.

  • @Adrian我真的想不出任何其他函数重载方法来解决这个问题.你注意到不允许部分超载,对吧?如果您认为更清楚,请与我们分享您的解决方案. (2认同)

Mat*_* M. 13

什么是专业化?

如果您真的想了解模板,那么您应该看一下函数式语言.C++中的模板世界是一个纯粹的功能性子语言.

在函数式语言中,使用模式匹配完成选择:

-- An instance of Maybe is either nothing (None) or something (Just a)
-- where a is any type
data Maybe a = None | Just a

-- declare function isJust, which takes a Maybe
-- and checks whether it's None or Just
isJust :: Maybe a -> Bool

-- definition: two cases (_ is a wildcard)
isJust None = False
isJust Just _ = True
Run Code Online (Sandbox Code Playgroud)

如您所见,我们重载了定义isJust.

好吧,C++类模板的工作方式完全相同.您提供了一个声明,其中说明了参数的数量和性质.它可以只是一个声明,也可以作为一个定义(你的选择),然后你可以(如果你愿意的话)提供模式的特化,并将它们与它们联系起一个不同的(否则它将是愚蠢的)类的版本.

对于模板函数,特化有点尴尬:它与重载解析有些冲突.因此,已经决定专业化将涉及非专业化版本,并且在重载解析期间不会考虑专业化.因此,选择正确函数的算法变为:

  1. 在常规函数和非专用模板中执行重载解析
  2. 如果选择了非专用模板,请检查是否存在可以更好匹配的专门化

(有关深入治疗,请参阅GotW#49)

因此,功能的模板专业化是第二区域公民(字面意思).就我而言,没有它们我们会更好:我还没有遇到一个模板专业化使用无法通过重载来解决的情况.

这是模板专业化吗?

不,这只是一个过载,这很好.事实上,重载通常像我们期望的那样工作,而专业化可能会令人惊讶(请记住我链接的GotW文章).


use*_*407 6

不允许非类,非变量的部分专业化,但是请注意:

计算机科学中的所有问题都可以通过另一层间接解决。-大卫·惠勒

添加一个类来转发函数调用可以解决此问题,下面是一个示例:

template <class Tag, class R, class... Ts>
struct enable_fun_partial_spec;

struct fun_tag {};

template <class R, class... Ts>
constexpr R fun(Ts&&... ts) {
  return enable_fun_partial_spec<fun_tag, R, Ts...>::call(
      std::forward<Ts>(ts)...);
}

template <class R, class... Ts>
struct enable_fun_partial_spec<fun_tag, R, Ts...> {
  constexpr static R call(Ts&&... ts) { return {0}; }
};

template <class R, class T>
struct enable_fun_partial_spec<fun_tag, R, T, T> {
  constexpr static R call(T, T) { return {1}; }
};

template <class R>
struct enable_fun_partial_spec<fun_tag, R, int, int> {
  constexpr static R call(int, int) { return {2}; }
};

template <class R>
struct enable_fun_partial_spec<fun_tag, R, int, char> {
  constexpr static R call(int, char) { return {3}; }
};

template <class R, class T2>
struct enable_fun_partial_spec<fun_tag, R, char, T2> {
  constexpr static R call(char, T2) { return {4}; }
};

static_assert(std::is_same_v<decltype(fun<int>(1, 1)), int>, "");
static_assert(fun<int>(1, 1) == 2, "");

static_assert(std::is_same_v<decltype(fun<char>(1, 1)), char>, "");
static_assert(fun<char>(1, 1) == 2, "");

static_assert(std::is_same_v<decltype(fun<long>(1L, 1L)), long>, "");
static_assert(fun<long>(1L, 1L) == 1, "");

static_assert(std::is_same_v<decltype(fun<double>(1L, 1L)), double>, "");
static_assert(fun<double>(1L, 1L) == 1, "");

static_assert(std::is_same_v<decltype(fun<int>(1u, 1)), int>, "");
static_assert(fun<int>(1u, 1) == 0, "");

static_assert(std::is_same_v<decltype(fun<char>(1, 'c')), char>, "");
static_assert(fun<char>(1, 'c') == 3, "");

static_assert(std::is_same_v<decltype(fun<unsigned>('c', 1)), unsigned>, "");
static_assert(fun<unsigned>('c', 1) == 4, "");

static_assert(std::is_same_v<decltype(fun<unsigned>(10.0, 1)), unsigned>, "");
static_assert(fun<unsigned>(10.0, 1) == 0, "");

static_assert(
    std::is_same_v<decltype(fun<double>(1, 2, 3, 'a', "bbb")), double>, "");
static_assert(fun<double>(1, 2, 3, 'a', "bbb") == 0, "");

static_assert(std::is_same_v<decltype(fun<unsigned>()), unsigned>, "");
static_assert(fun<unsigned>() == 0, "");
Run Code Online (Sandbox Code Playgroud)


Ika*_*per 6

我对迟到的答案感到抱歉,但我找到了一个解决方案,我在其他答案中没有看到解释(至少不是直接解释)。

函数不能部分特化,而类可以。这里可以发挥作用的是类内的静态函数。我们可以让它基本上在类专业化中移动“模板部分专业化”,并在其中创建标记为静态的函数。这将允许我们通过增加一点所需的代码行来构造我们的部分专用函数。

Printer让我们考虑如下不可用的部分专用函数(根本无法编译的代码)。

template <class T, class Trait = void>
void Printer(const T&);

template <class T>
void Printer<T, std::enable_if_t<std::is_floating_point_v<T>>>(const T& v){
    std::cout << "I m partially specialized for any floating point type." << std::endl;
}
template <class T>
void Printer<T, std::enable_if_t<std::is_integral_v<T>>>(const T& v){
    std::cout << "I m partially specialized for any integral type." << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

我们可以使用静态类函数使其工作,并将部分特化转移到类上,如下所示:

namespace detail{
    
    template<class T, class Trait = void>
    struct Specialized;

    template<class T>
    struct Specialized<T, std::enable_if_t<std::is_floating_point_v<T>>>
    {
        static void Printer(const T& v){
            std::cout << "I m specialized for any floating point type"<< std::endl;
        }
    };

    template<class T>
    struct Specialized<T, std::enable_if_t<std::is_integral_v<T>>>
    {
        static void Printer(const T& v){
            std::cout << "I m specialized for any integral type"<< std::endl;
        }
    };
}


template<class T>
void Printer(const T& v)
{
    detail::Specialized<T>::Printer(v);   
}
Run Code Online (Sandbox Code Playgroud)

结果有点长,但会以相对清晰的方式解决我们的问题。您可以在此处在 godbolt 上进行测试。

------ 编辑:感谢KROy的提示

通过将两个静态函数包装在一个结构体中并保留模板特化,可以使其变得更短:

namespace detail{
    struct Specialized{
        template<class T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
        static void Printer(const T& v){
            std::cout << "I'm specialized for integral types." << std::endl;
        }

        template<class T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
        static void Printer(const T& v){
            std::cout << "I'm specialized for floating point types." << std::endl;
        }
    };
}

template<class T>
void Printer(const T& v)
{
    detail::Specialized::Printer(v);   
}
Run Code Online (Sandbox Code Playgroud)

可以在这里在 godbolt 上进行测试。