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

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


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

12
задан Peter Alexander 8 March 2010 в 19:06
поделиться

2 ответа

Как сказано в литб, 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 до степени смешения похожа на оператор * , поэтому на самом деле нет в моем примере нужно специализироваться на 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 . Вызывающие абоненты использовали бы тот или иной и получили бы нужную функцию. Однако в исходном вопросе говорится о частичной специализации, поэтому можем ли мы притвориться, что специализация не является вариантом, без того, чтобы я фактически сделал HeapOfHam шаблоном класса?

3
ответ дан 2 December 2019 в 23:42
поделиться

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

1
ответ дан 2 December 2019 в 23:42
поделиться
Другие вопросы по тегам:

Похожие вопросы: