Как я могу заставить шаблонный оператор вывести правильный возвращаемый тип?

#c #templates

#c #шаблоны

Вопрос:

Допустим, мне нужны два ортогональных типа A и B, чтобы я мог написать

 A a = b1 * b2; // b1,b2 of type B
B b = a1 * a2; // a1,a2 of type A
  

Данные, которые они совместно используют, одинаковы, поэтому я попытался разработать дизайн на основе политик. Некоторый код:

 #include <type_traits>

struct isA {};
struct isB {};

template<typename T>
struct myClass
{
    int _data;

    template<typename U>
    myClass<U> operator * ( const myClass<T>amp; other );
};

template<typename T>
template<typename U>
myClass<U> myClass<T>::operator * ( const myClass<T>amp; other )
{
    // just an idea, will not be needed if correct instanciation
    static_assert( std::is_same<U,T>::value, "cannot return same type" );
   // ... here, some code
}


int main()
{
    myClass<isA> a1,a2;
    myClass<isB> b = a1 * a2;
}
  

Это не удается с:

 main.cpp: In function 'int main()':
main.cpp:26:25: error: no match for 'operator*' (operand types are
'myClass<isA>' and 'myClass<isA>')
    myClass<isB> b = a1 * a2;
main.cpp:12:16: note: candidate: 'template<class U> myClass<U> myClass<T>::operator*(const myClass<T>amp;) [with U = U; T = isA]'
  myClass<U> operator * ( const myClass<T>amp; other );
 main.cpp:12:16: note:   template argument deduction/substitution failed:
 main.cpp:26:27: note:   couldn't deduce template parameter 'U'
  

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

Мой вопрос (довольно простой): как я могу реализовать этот оператор?

Здесь не требуется шаблонная специализация, поведение одинаково для двух типов (но другие функции, не показанные здесь, будут иметь конкретную реализацию для каждого из типов). Но я хочу обеспечить соблюдение того факта, что вы не можете этого сделать: A a = a1 * a2;

Примечание: не удалось найти ни одного вопроса по этой теме, если вы его найдете, пожалуйста, дайте ссылку!

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

1. @Какой-то программист (насчет редактирования) Мммм, хорошо, но std::is_same — это только C 11…

2. То же замечание. [c ] сам по себе означает «текущую версию», если OP имеет доступ только к C 11, который должен быть виден в тегах.

3. C 11 (по крайней мере) следует считать базовым в наши дни. Если вы не спросите о чем-то конкретном, касающемся стандарта C 11, просто используйте c as tag .

4. @ В конце концов, какой-то программист согласен.

5. Существует ли ровно две версии, a и b? или их может быть несколько?

Ответ №1:

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

 namespace detail
{
    template<typename Out, typename In>
    MyClass<Out> times( const MyClass<In> amp; lhs, const MyClass<In> amp; rhs)
    {
        // shared code here
    }
}

myClass<isA> operator * ( const myClass<isB>amp; lhs, const myClass<isB>amp; rhs )
{ return detail::times<isA>(lhs, rhs); }

myClass<isB> operator * ( const myClass<isA>amp; lhs, const myClass<isA>amp; rhs )
{ return detail::times<isB>(lhs, rhs); }
  

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

1. Спасибо, хорошая идея!

Ответ №2:

Вы можете создать признак, который сопоставляется isA с isB и isB с isA .

 namespace detail
{
    template<typename>
    struct myClassTraits;

    template<>
    struct myClassTraits<isA>
    {
        using other_type = isB;
    };

    template<>
    struct myClassTraits<isB>
    {
        using other_type = isA;
    };
}

template<typename T>
struct myClass
{
    int _data;

    using times_t = myClass<typename detail::myClassTraits<T>::other_type>;

    times_t operator * ( const myClassamp; other );
};
  

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

1. Тоже хорошая идея. Я не вижу явного преимущества перед вашим другим ответом. Какой бы вы выбрали? В настоящее время я, вероятно, приму другой вариант, поскольку нахожу его немного «понятнее».

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

Ответ №3:

К сожалению, C не использует возвращаемый тип для вывода параметров шаблона (некоторые другие языки могут это делать), поэтому вы ничего не можете сделать с шаблоном.

Однако, чтобы сделать

 A a = b1 * b2; // b1,b2 of type B
  

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

 template <typename U>
myClass(const myClass<U>amp; other)  {} // copy conversion constructor
template <typename U>
myClass(myClass<U>amp;amp; other)  {} // move conversion constructor
  

так что

 A a = b1 * b2;
  

будет эквивалентно

 A a = A(b1 * b2);
  

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

1. Спасибо за идею, но не могу заставить это работать, смотрите Здесь (Coliru в настоящее время не работает). Мне пришлось добавить конструктор по умолчанию, но даже при этом он не компилируется.

2. @kebs вам нужно сделать operator* возвращающим значение того же типа, что и операнды: ideone.com/QmtyfH

3. @DmitryGordon: Нет, ему не нужно, чтобы возвращаемый тип совпадал с типом ввода. Ему нужен какой-то объект настройки политики, который может отображаться в возвращаемом типе operator* , если operator* он должен быть шаблоном.

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