自由功能的部分模板专业化-最佳做法 [英] Partial template specialization of free functions - best practices

查看:85
本文介绍了自由功能的部分模板专业化-最佳做法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

大多数C ++程序员应该知道,不允许对自由函数进行部分模板专业化.例如,以下是非法的C ++:

template <class T, int N>
T mul(const T& x) { return x * N; }

template <class T>
T mul<T, 0>(const T& x) { return T(0); }

// error: function template partial specialization ‘mul<T, 0>’ is not allowed

但是,允许使用类/结构 的部分模板专业化,并且可以利用该模板来模仿自由函数的部分模板专业化功能.例如,可以使用以下方法实现上一个示例中的目标目标:

template <class T, int N>
struct mul_impl
{
    static T fun(const T& x) { return x * N; }
};

template <class T>
struct mul_impl<T, 0>
{
    static T fun(const T& x) { return T(0); }
};

template <class T, int N>
T mul(const T& x)
{
    return mul_impl<T, N>::fun(x);
}

它体积更大,更简洁,但是可以完成工作-就mul的用户而言,他们可以得到所需的部分专业化.


我的问题是:在编写模板化的免费函数(供其他人使用)时,是否应自动将实现委派给类的静态方法函数,以便您的库用户可以随意实现部分专业化,还是您只是以常规方式编写模板化函数,并忍受人们无法对其进行专门化的事实?

解决方案

正如litb所说,ADL在可以工作的地方是优越的,这基本上是每当可以从调用参数中推断出模板参数的时候:

#include <iostream>

namespace arithmetic {
    template <class T, class S>
    T mul(const T& x, const S& y) { return x * y; }
}

namespace ns {
    class Identity {};

    // this is how we write a special mul
    template <class T>
    T mul(const T& x, const Identity&) {
        std::cout << "ADL works!\n";
        return x;
    }

    // this is just for illustration, so that the default mul compiles
    int operator*(int x, const Identity&) {
        std::cout << "No ADL!\n";
        return x;
    }
}

int main() {
    using arithmetic::mul;
    std::cout << mul(3, ns::Identity()) << "\n";
    std::cout << arithmetic::mul(5, ns::Identity());
}

输出:

ADL works!
3
No ADL!
5

重载+ ADL通过部分专用于S = ns::Identity的功能模板arithmetic::mul来实现您所要达到的目标.但是它确实依赖于调用方以允许ADL的方式来调用它,这就是为什么您从不显式调用std::swap的原因.

所以问题是,您希望库的用户必须对功能模板进行部分专业化?如果它们要专门针对类型(通常是算法模板的情况),请使用ADL.如果像示例中那样,它们将专门用于整数模板参数,那么我猜您必须委托给一个类.但是我通常不希望第三方定义乘以3的乘积-我的库将对整数进行 all 运算.我可以合理地期望第三方通过重音定义什么乘法.

请考虑一下,求幂运算可能是我使用的更好示例,因为我的arithmetic::muloperator*令人困惑,因此在我的示例中实际上没有必要专门研究mul.然后,我将专门针对第一个参数/ADL重载,因为对任何事物而言,身份就是身份".希望您能明白.

我认为ADL有一个缺点-它有效地使名称空间变平.如果我想使用ADL为我的课程实现" arithmetic::subsandwich::sub,那么我可能会遇到麻烦.我不知道专家该怎么说.

我的意思是

namespace arithmetic {
    // subtraction, returns the difference of lhs and rhs
    template<typename T>
    const T sub(const T&lhs, const T&rhs) { return lhs - rhs; }
}

namespace sandwich {
    // sandwich factory, returns a baguette containing lhs and rhs
    template<typename SandwichFilling>
    const Baguette sub(const SandwichFilling&lhs, const SandwichFilling&rhs) { 
      // does something or other 
    }
}

现在,我的类型是ns::HeapOfHam.我想利用std :: swap样式的ADL来编写自己的算术::: sub:

实现

namespace ns {
    HeapOfHam sub(const HeapOfHam &lhs, const HeapOfHam &rhs) {
        assert(lhs.size >= rhs.size && "No such thing as negative ham!");
        return HeapOfHam(lhs.size - rhs.size);
    }
}

我还想利用std :: swap样式的ADL来编写我自己的sandwich :: sub:

实现.

namespace ns {
    const sandwich::Baguette sub(const HeapOfHam &lhs, const HeapOfHam &rhs) {
        // create a baguette, and put *two* heaps of ham in it, more efficiently
        // than the default implementation could because of some special
        // property of heaps of ham.
    }
}

稍等片刻.我做不到,可以吗?具有相同参数和不同返回类型的不同名称空间中的两个不同函数:通常不存在问题,这就是名称空间的用途.但是我无法同时对它们两者进行ADL验证.可能我错过了一些明显的东西.

顺便说一句,在这种情况下,我可以完全专门研究arithmetic::subsandwich::sub中的每一个.调用方将一个或另一个using并获得正确的功能.但是,最初的问题是关于部分专业化的问题,因此,如果没有我实际上将HeapOfHam设为类模板的话,我们可以假装专业化不是一种选择吗?

As most C++ programmers should know, partial template specialization of free functions is disallowed. For example, the following is illegal C++:

template <class T, int N>
T mul(const T& x) { return x * N; }

template <class T>
T mul<T, 0>(const T& x) { return T(0); }

// error: function template partial specialization ‘mul<T, 0>’ is not allowed

However, partial template specialization of classes/structs is allowed, and can be exploited to mimic the functionality of partial template specialization of free functions. For example, the target objective in the last example can be achieved by using:

template <class T, int N>
struct mul_impl
{
    static T fun(const T& x) { return x * N; }
};

template <class T>
struct mul_impl<T, 0>
{
    static T fun(const T& x) { return T(0); }
};

template <class T, int N>
T mul(const T& x)
{
    return mul_impl<T, N>::fun(x);
}

It's more bulky and less concise, but it gets the job done -- and as far as users of mul are concerned, they get the desired partial specialization.


My questions is: when writing templated free functions (that are intended to be used by others), should you automatically delegate the implementation to a static method function of a class, so that users of your library may implement partial specializations at will, or do you just write the templated function the normal way, and live with the fact that people won't be able to specialize them?

解决方案

As litb says, ADL is superior where it can work, which is basically whenever the template parameters can be deduced from the call parameters:

#include <iostream>

namespace arithmetic {
    template <class T, class S>
    T mul(const T& x, const S& y) { return x * y; }
}

namespace ns {
    class Identity {};

    // this is how we write a special mul
    template <class T>
    T mul(const T& x, const Identity&) {
        std::cout << "ADL works!\n";
        return x;
    }

    // this is just for illustration, so that the default mul compiles
    int operator*(int x, const Identity&) {
        std::cout << "No ADL!\n";
        return x;
    }
}

int main() {
    using arithmetic::mul;
    std::cout << mul(3, ns::Identity()) << "\n";
    std::cout << arithmetic::mul(5, ns::Identity());
}

Output:

ADL works!
3
No ADL!
5

Overloading+ADL achieves what you would have achieved by partially specializing the function template arithmetic::mul for S = ns::Identity. But it does rely on the caller to call it in a way which allows ADL, which is why you never call std::swap explicitly.

So the question is, what do you expect users of your library to have to partially specialize your function templates for? If they're going to specialize them for types (as is normally the case with algorithm templates), use ADL. If they're going to specialize them for integer template parameters, as in your example, then I guess you have to delegate to a class. But I don't normally expect a third party to define what multiplication by 3 should do - my library will do all the integers. I could reasonably expect a third party to define what multiplication by an octonion will do.

Come to think of it, exponentiation might have been a better example for me to use, since my arithmetic::mul is confusingly similar to operator*, so there's no actual need to specialize mul in my example. Then I'd specialize/ADL-overload for the first parameter, since "Identity to the power of anything is Identity". Hopefully you get the idea, though.

I think there is a downside to ADL - it effectively flattens namespaces. If I want to use ADL to "implement" both arithmetic::sub and sandwich::sub for my class, then I could be in trouble. I don't know what the experts have to say about that.

By which I mean:

namespace arithmetic {
    // subtraction, returns the difference of lhs and rhs
    template<typename T>
    const T sub(const T&lhs, const T&rhs) { return lhs - rhs; }
}

namespace sandwich {
    // sandwich factory, returns a baguette containing lhs and rhs
    template<typename SandwichFilling>
    const Baguette sub(const SandwichFilling&lhs, const SandwichFilling&rhs) { 
      // does something or other 
    }
}

Now, I have a type ns::HeapOfHam. I want to take advantage of std::swap-style ADL to write my own implementation of arithmetic::sub:

namespace ns {
    HeapOfHam sub(const HeapOfHam &lhs, const HeapOfHam &rhs) {
        assert(lhs.size >= rhs.size && "No such thing as negative ham!");
        return HeapOfHam(lhs.size - rhs.size);
    }
}

I also want to take advantage of std::swap-style ADL to write my own implementation of sandwich::sub:

namespace ns {
    const sandwich::Baguette sub(const HeapOfHam &lhs, const HeapOfHam &rhs) {
        // create a baguette, and put *two* heaps of ham in it, more efficiently
        // than the default implementation could because of some special
        // property of heaps of ham.
    }
}

Hang on a minute. I can't do that, can I? Two different functions in different namespaces with the same parameters and different return types: not usually a problem, that's what namespaces are for. But I can't ADL-ify them both. Possibly I'm missing something really obvious.

Btw, in this case I could just fully specialize each of arithmetic::sub and sandwich::sub. Callers would using one or the other, and get the right function. The original question talks about partial specialization, though, so can we pretend that specialization is not an option, without me actually making HeapOfHam a class template?

这篇关于自由功能的部分模板专业化-最佳做法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆