Избегайте столкновения элементов в шаблонах C

#c #templates

#c #шаблоны

Вопрос:

У меня есть класс

 template <typename T, typename W>
class A {
    void foo(W);
    void foo(T);
    void foo(int);
}
 

Когда T=int , W=int , или W=T , этот класс не может скомпилироваться. Как я могу заставить методы иметь приоритет друг над другом?

Мне нужен приоритет W > T > int . Поэтому, если W=T , foo(T) игнорируется и foo(W) вызывается. Если T=int , foo(int) игнорируется и foo(T) вызывается.

Компилятор — VS2012, но у меня тоже есть Linux, и я также рассмотрю решения GCC / Clang. Все, что компилируется на любом основном компиляторе, идет, но только если вы укажете, на каких компиляторах оно работает.

Комментарии:

1. Похоже, задание для std::enable_if .

2. @Deduplicator Я думаю, что в случае enable_if сбоя класс не сможет скомпилироваться.

3. Я почти не решаюсь спрашивать, но мое любопытство часто является моим врагом; Почему ? Для чего это используется?

4. std::enable_if<std::is_same<A, B>::value, A>::type этот тип компилируется, и он действителен, только если A и B совпадают. Вы можете адаптировать его к своим потребностям. en.cppreference.com/w/cpp/types/enable_if вы также должны указать версию C , на которую вы ориентируетесь.

5. enable_if при использовании в функции-члене может работать только с параметрами шаблона этой функции-члена (а не с параметрами шаблона шаблона класса, содержащего эту функцию-член). Поэтому я не понимаю, как это будет работать здесь.

Ответ №1:

Я бы отметил dispatch . Переопределение диспетчеризации легко понять и масштабируется.

Мы начинаем с идеальной пересылки:

 template<class U> void foo(Uamp;amp;u){
  foo( std::forward<U>(u), std::is_convertible<U, W>{}, std::is_convertible<U,T>{} );
}
 

он создает типы тегов, в данном случае типы true или false, для отправки.

Этот:

 void foo( W, std::true_type, ... );
 

улавливает все, что может быть преобразовано в W.

Далее мы блокируем этот:

 void foo( T, std::false_type, std::true_type );
 

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

Наконец, этот:

 void foo( int, std::false_type, std::false_type );
 

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

Возможны более причудливые типы тегов или выполнение отправки по одному за раз.

Извините за опечатки.

Я использую одну функцию C 11 — {} для создания объекта — выше. Если вашему компилятору не хватает поддержки этой функции C 11, просто обновите свой компилятор, это 2014 год, смирись с этим. В противном случае замените {} на () .

Комментарии:

1. Вау! Приятно! Я понятия не имел об этом шаблоне. Попробую.

2. О каком хите производительности мы говорим? Может ли компилятор разрешить это время компиляции?

3. @pythonnut Да. Это true_type типы, следовательно, время компиляции. Пересылка — это не что иное, как приведение ссылок (noop). Вы можете столкнуться с сбоем вашего оптимизатора, потому что он отказывается без уважительной причины, поэтому, если это очень важно, profile, но на самом деле ничего не делается, и этот метод широко используется std .

4. Я что-то упускаю, или ваши паренты отправки не совпадают?

5. вероятно, @pythonnut. Дизайн хороший, но мой компьютер отказался от ghost, поэтому не тестировался. 😉 Исправлена ) проблема <-> > . is_convertible может быть написано неправильно, аргументы типа также могут быть обратными. Ах ... , тоже ложный. Теперь можно компилировать!

Ответ №2:

Используйте std::enable_if :

 #include <type_traits>

template <typename T, typename W>
struct A {
    void foo(W) {}
    template<typename XT=T> typename std::enable_if<std::is_same<XT,T>::value
      amp;amp; !std::is_same<T, W>::value, void>::type foo(T) {}
    template<typename XT=int> typename std::enable_if<std::is_same<XT,int>::value
      amp;amp; !std::is_same<int, T>::value
      amp;amp; !std::is_same<int, W>::value, void>::type foo(int) {}
};
 

Добавлено для тестирования:

 template struct A<short,char>;
template struct A<char,char>;
template struct A<char,int>;
template struct A<int,char>;
template struct A<int, int>;

struct S {};

int main() {
    A<S, int>{}.foo(S{});
}
 

Комментарии:

1. Это не работает для меня (g -4.9); по-видимому, 0 не может быть преобразован в значение enable_if::type * . Это ошибка в gcc? (В любом случае, он работает с nullptr вместо 0 )

2. Также не будет компилироваться в Visual Studio, поскольку он не поддерживает аргументы шаблона по умолчанию для элементов.

3. @Брайан: Изменен и протестирован повторно.

Ответ №3:

Для соответствующей части вашего шаблона вы можете использовать спецификации:

 template <typename U, typename W>
struct Foo
{
    void f(U);
    void f(W);
};

template <typename T>
struct Foo<T, T>
{
    void f(T);
};
 

Для остальной части вашего класса или шаблона класса вы можете наследовать Foo<A, B> , чтобы исключить общий код из части, которая нуждается в специализации:

 template <typename A, typename B>
struct TheClass : Foo<A, B>
{
    // common code
};
 

Комментарии:

1. Понадобилось бы 3 специализации, плюс базовая версия!

Ответ №4:

Попробуйте специализации шаблонов:

 template <typename T, typename W>
class A {
    void foo(W);
    void foo(T);
    void foo(int);
};

template <typename T>
class A<T, T> {
    void foo(T);
    void foo(int);
};
template <>
class A<int, int> {
    void foo(int);
};
 

Комментарии:

1. Потребуется 3 специализации плюс базовая версия!

2. Кроме того, вы должны дублировать оставшуюся часть содержимого класса для каждой специализации!

3. @Matt McNabb нет, если вы наследуете, что предлагают другие.

Ответ №5:

Вот решение без специализации A, но с двумя вспомогательными структурами в нескольких формах.

 #include <iostream>

template<typename T, typename W>
struct T_type { typedef T type; };
template<typename W>
struct T_type<W, W>  { typedef void* type; /*dummy type*/};

template<typename T, typename W>
struct int_type { typedef int type; };
template<typename W>
struct int_type<int, W> { typedef void** type; /*dummy type*/};
template<typename T>
struct int_type<T, int> { typedef void** type; /*dummy type*/};
template<>
struct int_type<int, int> { typedef void** type; /*dummy type*/};

template<typename T, typename W>
class A {
public:
    void foo(W w) {
        std::cout << "foo(W)" << std::endl;
    }
    void foo(typename T_type<T, W>::type t) {
        std::cout << "foo(T)" << std::endl;
    }
    void foo(typename int_type<T, W>::type i) {
        std::cout << "foo(int)" << std::endl;
    }
};

int main() {
    std::cout << "A<float, char>" << std::endl;
    A<float, char> a;
    a.foo(1.0f);
    a.foo('1');
    a.foo(1);

    std::cout << "A<float, float>" << std::endl;
    A<float, float> b;
    b.foo(1.0f);
    b.foo(1);

    std::cout << "A<int, int>" << std::endl;
    A<int, int> c;
    c.foo(1);

    return 0;
}