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

Как должно знать большинство программистов на 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, они получают желаемую частичную специализацию.


Мои вопросы: при написании шаблонных бесплатных функций (которые предназначены для использования другими), следует ли вам автоматически делегировать реализацию статической функции метода класса, чтобы пользователи вашей библиотеки могли реализовать частичную специализацию по своему желанию или сделать вы просто пишете шаблонную функцию обычным способом и живете с тем фактом, что люди не смогут их специализировать?


person Peter Alexander    schedule 08.03.2010    source источник
comment
Я думаю, это зависит от обстоятельств. В вашем случае вы называете это как fun<U, N>(u), поэтому вы не можете перегрузить (N нигде не отображается в параметре). Но я думаю, что если возможна перегрузка, это предпочтительный способ. Отличный пример - std::swap, std::begin или std::end (где последние два являются функциями C ++ 0x). Обратите внимание, что статья Саттера была написана 9 лет назад. Не уверен, что он по-прежнему рекомендует делать это как делегат класса. И я думаю, что это плохо масштабируется: не будет работать с ADL - вам придется возиться со всевозможными пространствами имен и специализировать их шаблоны. Нет ничего хорошего   -  person Johannes Schaub - litb    schedule 08.03.2010


Ответы (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-overload для первого параметра, так как «Идентичность во власти чего-либо есть Идентичность». Надеюсь, вы уловили идею.

Я думаю, что у 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. Я хочу воспользоваться ADL в стиле std :: swap, чтобы написать свою собственную реализацию 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);
    }
}

Я также хочу воспользоваться ADL в стиле std :: swap, чтобы написать свою собственную реализацию 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 шаблоном класса?

person Community    schedule 08.03.2010
comment
Не уверен в проблеме sub для плоских пространств имен. Думаю, такая же проблема возникла бы, если бы sub была функцией-членом. Каким будет операнд этих двух подпрограмм? Если бутерброд: я не могу представить, как мы могли бы арифметически подставить бутерброд, поэтому sandwich, определенный в arithmetic, кажется маловероятным. Если MyInt: поскольку неявные преобразования фактически не рассматриваются (если sandwich имеет (MyInt) (для количества сырных тарелок?) Ctor и мы вызываем sub(a, b), мы, конечно, не найдем sub в бутерброде), то я не похож на проблема на первых порах. Буду рад, если вы подробнее остановитесь на этом. - person Johannes Schaub - litb; 09.03.2010
comment
@litb: Я не совсем слежу за вами, поэтому я отредактирую и посмотрим, говорим ли мы об одном и том же. - person Steve Jessop; 09.03.2010
comment
@lit: готово. Если вы не знаете ответа, возможно, я задам вопрос. - person Steve Jessop; 09.03.2010
comment
@ Стив, а теперь понимаю. Я думаю, вам стоит добавить вопрос по этому поводу, это интересно. Я думаю, что один из способов решить эту проблему - это спросить, действительно ли это вообще в пользу ADL. Я рассматриваю ADL как своего рода интерфейс к классу: куча ветчины будет связана с заменой багета - что не звучит так, как будто это должно быть частью интерфейса кучи ветчины. В то время как такие вещи, как замена двух ветчин или получение размера ветчины, были бы хорошими кандидатами на функции интерфейса кучи ветчины. - person Johannes Schaub - litb; 09.03.2010
comment
@litb: Тогда я попробую придумать лучший пример. Предположим, что в английском языке swap был неоднозначным и означал не только обмен двумя значениями (std :: swap), но и выполнение свертки двух объектов (Calculus :: swap). У меня была бы такая же проблема, что я не могу перегрузить ADL для HeapOfHam. Если решения нет, возможно, другие пространства имен, кроме std, не должны использовать этот прием для перегрузки алгоритмов из-за страха конфликта имен. Это оставляет делегирование классу, как в коде запрашивающего. - person Steve Jessop; 09.03.2010
comment
Возможно, метод ADL для фабрик состоит в том, чтобы передать объект identity, например: sub(identity<KindOfSub>(), part1, part2), таким образом ADL будет искать в пространствах имен KindOfSub и part1 и part2. Итак, для заводов первый параметр обозначает тип того, что нужно создать. У разных типов субмарин могут быть разные методы изготовления сэндвича, и фабрика будет отделена от чисто операционных функций part1 и part2 с помощью первого параметра идентичности. - person Johannes Schaub - litb; 09.03.2010
comment
Если подумать, это может быть та же проблема, что и имена функций-членов. Если Concept1 определяет разрешенное выражение, требующее функции-члена renuberate(void), а Concept2 также определяет функцию-член renuberate(void), которая делает что-то другое, то HeapOfHam не может реализовать обе концепции. Это хуже, если автор HeapOfHam никогда не слышал об исчислении пространств имен. Он реализует обмен, не являющийся членом, то есть std :: swap, но кто-то делает using calculus::swap; swap(heap,heap);, думая получить в худшем случае реализацию calculus::swap по умолчанию. Неправильный. - person Steve Jessop; 09.03.2010
comment
Итак, очевидно, что calculus неправильно определять подкачку функций там, где ожидается, что типы будут использовать эту технику, потому что все знают, что std::swap существует и будет конфликтовать. Но если двусмысленный глагол находится не в пространстве имен std, а в двух несвязанных библиотеках, которые обе могут использоваться с моим классом, я застрял. С делегированием классу я бы не застрял, я бы просто реализовал две частичные специализации класса. - person Steve Jessop; 09.03.2010

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

person Rodney Schuler    schedule 08.03.2010