Частичная специализация шаблона функции - лучшие практики

как должно знать большинство программистов на 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

однако частичная специализация шаблонов классов / структур is разрешено и может использоваться для имитации функциональности частичной специализации шаблонов свободных функций. Например, цель в последнем примере может быть достигнуто с помощью:

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 ответов


как говорит 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 - Моя библиотека будет делать все чисел. Я можно было бы разумно ожидать, что третья сторона определит, какое умножение на октонионных будет делать.

подумайте об этом, возведение в степень могло бы быть лучшим примером для меня, так как мой arithmetic::mul сходный до степени смешения operator*, так что нет никакой реальной необходимости специализироваться mul в моем примере. Тогда я бы специализировал / ADL-перегрузку для первого параметра, так как"идентичность власти чего-либо-это идентичность". Надеюсь, вы поняли, хотя.

я думаю, что есть недостаток ADL-он эффективно выравнивает пространства имен. Если я хочу использовать ADL для "реализации" обоих arithmetic::sub и sandwich::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, чтобы написать собственную реализацию арифметики:: 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, чтобы написать собственную реализацию 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.
    }
}

минуточку. Я не могу этого сделать, не так ли? Две разные функции в разных пространствах имен с одинаковыми параметрами и разными типами возвращаемых значений: обычно это не проблема, для этого существуют пространства имен. Но я не могу назвать их обоих. Возможно, я упускаю что-то очевидное.

кстати, в этом случае я мог бы просто полностью специализироваться на каждом из arithmetic::sub и sandwich::sub. Звонившие бы using одно или другое, и получите правую функцию. Однако исходный вопрос говорит о частичной специализации, поэтому можем ли мы притвориться, что специализация не является вариантом, без меня на самом деле делая HeapOfHam шаблоном класса?


Если вы пишете библиотеку для использования в другом месте или другими людьми, сделайте вещь struct/class. Это больше кода, но пользователи вашей библиотеки (возможно, в будущем вам!) буду Вам благодарен. Если это один код использования, потеря частичной специализации не повредит вам.