为什么我似乎可以为函数模板定义部分特化?

发布于 2024-12-14 13:16:14 字数 870 浏览 3 评论 0原文

我知道下面的代码是一个类的部分专业化:

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


// partial specialization: both template parameters have same type 
template <typename T> 
class MyClass<T,T> { 
  … 
}; 

我还知道 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;
}

I know that the below code is a partial specialization of a class:

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


// partial specialization: both template parameters have same type 
template <typename T> 
class MyClass<T,T> { 
  … 
}; 

Also I know that C++ does not allow function template partial specialization (only full is allowed). But does my code mean that I have partially specialized my function template for one/same type arguments? Because it works for Microsoft Visual Studio 2010 Express! If no, then could you please explain the partial specialization concept?

#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;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

满身野味 2024-12-21 13:16:15

根据标准,函数部分特化是不允许的。在这个例子中,你实际上是在重载&没有专门化 max 函数。
如果允许的话,它的语法应该看起来有点如下:

// Partial specialization is not allowed by the spec, though!
template <typename T> 
inline T const& max<T,T> (T const& a, T const& b)
{            //    ^^^^^ <--- supposed specializing here as an example
  return a; // can be anything of type T
}

在函数模板的情况下,仅允许完全专业化 C++ 标准。
有一些编译器扩展允许部分专业化,但在这种情况下代码会失去可移植性!

Function partial specialization is not allowed yet as per the standard. In the example, you are actually overloading & not specializing the max<T1,T2> function.
Its syntax should have looked somewhat like below, had it been allowed:

// Partial specialization is not allowed by the spec, though!
template <typename T> 
inline T const& max<T,T> (T const& a, T const& b)
{            //    ^^^^^ <--- supposed specializing here as an example
  return a; // can be anything of type T
}

In the case of a function templates, only full specialization is allowed by the C++ standard.
There are some compiler extensions which allows partial specialization, but the code looses its portability in such case!

蓝海似她心 2024-12-21 13:16:15

由于不允许部分专业化(正如其他答案所指出的那样),您可以使用 std::is_samestd::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);
}

输出:

$ ./a.out 
>>> messing with ints! works
>>> messing with floats! 2

编辑:如果您需要能够处理剩下的所有其他案例,您可以添加一个定义,声明已处理的案例不应匹配 - 否则您' d 陷入模糊的定义。定义可以是:

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");
}

产生:

$ ./a.out 
>>> messing with ints! works
>>> messing with floats! 2
>>> messing with unknown stuff! either

虽然这个所有情况看起来有点无聊,但因为你必须告诉编译器你已经完成的所有事情,所以处理最多 5 个或一个是相当可行的还有几个专业。

Since partial specialization is not allowed -- as other answers pointed --, you could work around it using std::is_same and std::enable_if, as below:

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);
}

Output:

$ ./a.out 
>>> messing with ints! works
>>> messing with floats! 2

Edit: In case you need to be able to treat all the other cases left, you could add a definition which states that already treated cases should not match -- otherwise you'd fall into ambiguous definitions. The definition could be:

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");
}

Which produces:

$ ./a.out 
>>> messing with ints! works
>>> messing with floats! 2
>>> messing with unknown stuff! either

Although this all-cases thing looks a bit boring, since you have to tell the compiler everything you've already done, it's quite doable to treat up to 5 or a few more specializations.

鲜血染红嫁衣 2024-12-21 13:16:15

什么是专业化?

如果你真的想了解模板,你应该看看函数式语言。 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

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

嗯,C++ 类模板的工作方式完全相同。您提供一个 main 声明,说明参数的数量和性质。它可以只是一个声明,也可以充当定义(您的选择),然后您可以(如果您愿意)提供模式的专业化并将它们关联到该类的不同(否则会很愚蠢)版本。

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

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

(对于内部模板)深度处理,请参见 GotW #49

因此,函数的模板专门化是第二个 -地区公民 (字面上地)。就我而言,没有它们我们会更好:我还没有遇到过无法通过重载来解决模板专业化使用的情况。

这是模板专业化吗?

不,这只是超载,这没关系。事实上,重载通常会按照我们期望的方式工作,而专业化可能会令人惊讶(记住我链接的 GotW 文章)。

What is specialization ?

If you really want to understand templates, you should take a look at functional languages. The world of templates in C++ is a purely functional sublanguage of its own.

In functional languages, selections are done using Pattern Matching:

-- 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

As you can see, we overload the definition of isJust.

Well, C++ class templates work exactly the same way. You provide a main declaration, that states the number and nature of the parameters. It can be just a declaration, or also acts as a definition (your choice), and then you can (if you so wish) provide specializations of the pattern and associate to them a different (otherwise it would be silly) version of the class.

For template functions, specialization is somewhat more awkward: it conflicts somewhat with overload resolution. As such, it has been decided that a specialization would relate to a non-specialized version, and specializations would not be considered during overload resolution. Therefore, the algorithm for selecting the right function becomes:

  1. Perform overload resolution, among regular functions and non-specialized templates
  2. If a non-specialized template is selected, check if a specialization exist for it that would be a better match

(for on in-depth treatment, see GotW #49)

As such, template specialization of functions is a second-zone citizen (literally). As far as I am concerned, we would be better off without them: I have yet to encounter a case where a template specialization use could not be solved with overloading instead.

Is this a template specialization ?

No, it is simply an overload, and this is fine. In fact, overloads usually work as we expect them to, while specializations can be surprising (remember the GotW article I linked).

青萝楚歌 2024-12-21 13:16:15

不允许非类、非变量部分专业化,但如上所述:

电脑上的所有问题
科学可以解决
另一个间接级别。 ——大卫·惠勒

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

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, "");

Non-class, non-variable partial specialization is not allowed, but as said:

All problems in computer
science can be solved by
another level of indirection. —— David Wheeler

Adding a class to forward the function call can solve this, here is an example:

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, "");
无法言说的痛 2024-12-21 13:16:15

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

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

让我们考虑如下不可用的部分专用函数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;
}

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

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);   
}

它的结果有点长,但会以相对清晰的方式解决我们的问题。
您可以在 此处 在 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);   
}

它可以在 godbolt 这里

I m sorry for the late answer, but i ve found a solution which i don't see explained (not directly at least) in the other answers.

A function cannot be partially specialized, while a class can.
What can do the trick here is a static function inside class.
We can make it works basically moving the "template partial specialization" inside a class specialization and creating inside it the function marked as static. This will allow us, by incrementing a bit the lines of code required, to construct our partially specialized function.

Let's consider the unavailable partially specialized function Printer as follow (code which doesn't compile at all).

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;
}

We can make it works using static class functions and moving the partial specialization on class instead like this:

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);   
}

It results a bit longer but would solve our issue with a relatively clear way.
You can test it on godbolt here.

------ EDIT : Thanks to KROy for the hint

It can be made even shorter by just wrapping the two static functions inside a struct leaving the template specialization on them:

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);   
}

It can be tested on godbolt here.

烂人 2024-12-21 13:16:15

不可以。例如,您可以合法地专门化 std::swap,但您不能合法地定义自己的重载。这意味着您无法使 std::swap 适用于您自己的自定义类模板。

重载和部分特化在某些情况下可以产生相同的效果,但远非全部。

No. For example, you can legally specialize std::swap, but you cannot legally define your own overload. That means that you cannot make std::swap work for your own custom class template.

Overloading and partial specialization can have the same effect in some cases, but far from all.

沫雨熙 2024-12-21 13:16:15

迟到的答案,但一些迟到的读者可能会发现它很有用:有时,一个辅助函数 - 设计得可以专门化 - 也可以解决问题。

所以让我们想象一下,这就是我们试图解决的问题:

template <typename R, typename X, typename Y>
void function(X x, Y y)
{
    R* r = new R(x);
    f(r, y); // another template function?
}

// for some reason, we NEED the specialization:
template <typename R, typename Y>
void function<R, int, Y>(int x, Y y) 
{
    // unfortunately, Wrapper has no constructor accepting int:
    Wrapper* w = new Wrapper();
    w->setValue(x);
    f(w, y);
}

好吧,部分模板函数特化,我们不能这样做......所以让我们将特化所需的部分“导出”到辅助函数中,特化它一个并使用它:

template <typename R, typename T>
R* create(T t)
{
    return new R(t);
}
template <>
Wrapper* create<Wrapper, int>(int n) // fully specialized now -> legal...
{
    Wrapper* w = new Wrapper();
    w->setValue(n);
    return w;
}

template <typename R, typename X, typename Y>
void function(X x, Y y)
{
    R* r = create<R>(x);
    f(r, y); // another template function?
}

可能很有趣,特别是如果替代方案(正常重载而不是专门化,鲁本斯提出的解决方法,... – 并不是说​​这些不好或者我的更好,只是另一个一)将共享相当多的通用代码。

Late answer, but some late readers might find it useful: Sometimes, a helper function – designed such that it can be specialised – can solve the issue, too.

So let's imagine, this is what we tried to solve:

template <typename R, typename X, typename Y>
void function(X x, Y y)
{
    R* r = new R(x);
    f(r, y); // another template function?
}

// for some reason, we NEED the specialization:
template <typename R, typename Y>
void function<R, int, Y>(int x, Y y) 
{
    // unfortunately, Wrapper has no constructor accepting int:
    Wrapper* w = new Wrapper();
    w->setValue(x);
    f(w, y);
}

OK, partial template function specialisation, we cannot do that... So let's "export" the part needed for specialisation into a helper function, specialize that one and use it:

template <typename R, typename T>
R* create(T t)
{
    return new R(t);
}
template <>
Wrapper* create<Wrapper, int>(int n) // fully specialized now -> legal...
{
    Wrapper* w = new Wrapper();
    w->setValue(n);
    return w;
}

template <typename R, typename X, typename Y>
void function(X x, Y y)
{
    R* r = create<R>(x);
    f(r, y); // another template function?
}

This can be interesting especially if the alternatives (normal overloads instead of specialisations, the workaround proposed by Rubens, ... – not that these are bad or mine is better, just another one) would share quite a lot of common code.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文