自由函数的部分模板专业化 - 最佳实践

发布于 2024-08-23 21:43:25 字数 923 浏览 1 评论 0原文

大多数 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 的用户而言,他们得到所需的部分专业化。


我的问题是:在编写模板化自由函数(旨在供其他人使用)时,您是否应该自动将实现委托给类的静态方法函数,以便您的库的用户可以随意实现部分专业化,或者做您只是以正常方式编写模板化函数,并接受人们无法专门化它们的事实?

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?

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

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

发布评论

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

评论(2

空宴 2024-08-30 21:43:25

正如 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 实现了通过部分专门化函数模板arithmetic:: 实现的目标: mul 代表 S = ns::Identity。但它确实依赖于调用者以允许 ADL 的方式调用它,这就是为什么您从不显式调用 std::swap 的原因。

所以问题是,您希望库的用户必须部分专门化您的函数模板来做什么?如果他们要将它们专门用于类型(通常是算法模板的情况),请使用 ADL。如果他们打算将它们专门用于整数模板参数(如您的示例所示),那么我想您必须委托给一个类。但我通常不期望第三方定义乘以 3 应该做什么 - 我的库将处理所有整数。我可以合理地期望第三方定义 octonion 的乘法会做什么。

想想看,求幂可能是我使用的更好的例子,因为我的 arithmetic::mul 与 operator* 非常相似,所以没有实际需要在我的示例中专门化 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 来编写我自己的三明治::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 实现一分钟。我不能那样做,可以吗?不同命名空间中的两个不同函数具有相同的参数和不同的返回类型:通常不是问题,这就是命名空间的用途。但我无法将它们都 ADL 化。可能我错过了一些非常明显的东西。

顺便说一句,在这种情况下,我可以完全专门化 arithmetic::subsandwich::sub 中的每一个。调用者将使用其中之一,并获得正确的函数。不过,最初的问题讨论的是部分专业化,那么我们是否可以假装专业化不是一种选择,而无需我实际将 HeapOfHam 作为类模板?

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?

信仰 2024-08-30 21:43:25

如果您正在编写一个要在其他地方使用或由其他人使用的库,请执行结构/类的操作。这是更多的代码,但你的图书馆的用户(可能是未来的你!)会感谢你。如果这是一个使用代码,那么部分专业化的损失不会对您造成伤害。

If you are writing a library to be use elsewhere or by other people do the struct/class thing. It is more code but the users of your library (possibly a future you!) will thank you. IF this is one use code, the loss of partial specialization will not hurt you.

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