13

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


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

4

2 回答 2

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

Overloading+ADL 可以通过部分专门arithmetic::mulS = ns::Identity. 但它确实依赖于调用者以允许 ADL 的方式调用它,这就是为什么你从不std::swap显式调用的原因。

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

想一想,求幂对我来说可能是一个更好的例子,因为 myarithmetic::mul与 令人困惑地相似operator*,所以实际上没有必要专门mul研究我的例子。然后我会为第一个参数专门化/ADL-overload,因为“身份的力量就是身份”。不过,希望你能明白这一点。

我认为 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.
    }
}

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

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

于 2010-03-08T23:03:14.993 回答
1

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

于 2010-03-08T19:33:15.477 回答