#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. Да, из комментариев к вопросу я вижу, что он нужен только для двух типов. Но из исходного вопроса я подумал, что может быть более двух типов, которые необходимо преобразовать друг в друга