自由功能的部分模板专业化 - 最佳实践

时间:2010-03-08 19:06:01

标签: c++ partial-specialization non-member-functions

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


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

2 个答案:

答案 0 :(得分:3)

正如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::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-style ADL来编写我自己的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);
    }
}

我也想利用std :: swap-style 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.
    }
}

等一下。我不能那样做,可以吗?具有相同参数和不同返回类型的不同命名空间中的两个不同函数:通常不是问题,这就是命名空间的用途。但我不能ADL-ify他们俩。可能我错过了一些非常明显的东西。

顺便说一句,在这种情况下,我可以完全专注于arithmetic::subsandwich::sub。呼叫者将using一个或另一个,并获得正确的功能。最初的问题谈到了部分专业化,所以我们可以假装专业化不是一种选择,没有我实际上让HeapOfHam成为一个类模板吗?

答案 1 :(得分:1)

如果您正在编写要在别处或其他人使用的库,请执行struct / class。这是更多的代码,但你的图书馆的用户(可能是你的未来!)会感谢你。如果这是一个使用代码,部分专业化的损失不会伤害你。