Частичная шаблонная специализация бесплатных функций - лучшие практики - PullRequest
12 голосов
/ 08 марта 2010

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

3 голосов
/ 09 марта 2010

Как говорит 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. Я хочу использовать 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.
    }
}

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

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

1 голос
/ 08 марта 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...