Специализация шаблона конструктора Variadic шаблона класса
Здесь класс с конструктором variadic и его специализациями для копирования и перемещения из временного.
template<class Obj>
class wrapper {
protected:
Obj _Data;
public:
wrapper(const wrapper<Obj>& w): _Data(w._Data) {}
wrapper(wrapper<Obj>&& w):
_Data(std::forward<Obj>(w._Data)) {}
template<class ...Args>
wrapper(Args&&... args):
_Data(std::forward<Args>(args)...) {}
inline Obj& operator()() { return _Data; }
virtual ~wrapper() {}
};
Когда я использую одну из таких специализаций, как эта
wrapper<int> w1(9);
wrapper<int> w2(w1);
Я получаю сообщение об ошибке: тип w1
выводится как int
.
Выход из VS2012:
error C2440: 'initializing' : cannot convert from 'win::util::wrapper<int>' to 'int'
Как решить эту проблему?
Ответы
Ответ 1
Вас укусит жадный идеальный конструктор переадресации.
wrapper<int> w2(w1);
В приведенной выше строке улучшающий конструктор пересылки лучше сопоставляется с конструктором копирования, потому что Args
выводится как wrapper<int>&
.
Быстрое решение заключается в том, чтобы изменить строку выше на
wrapper<int> w2(static_cast<wrapper<int> const&>(w1));
это правильно вызывает конструктор копирования, но, помимо излишней подробности, не решает основной проблемы.
Чтобы решить исходную проблему, вам необходимо условно отключить идеальный конструктор переадресации, если Args
совпадает с wrapper<Obj>
.
Вот отличное сообщение в блоге, описывающее проблему, и как ее решить. Подводя итог, вам нужно изменить определение конструктора переадресации на
template <typename... Args,
DisableIf<is_related<wrapper<Obj>, Args...>::value>...>
wrapper(Args&&... args):
_Data(std::forward<Args>(args)...) {}
где is_related
определяется как
template <typename T, typename... U>
struct is_related : std::false_type {};
template <typename T, typename U>
struct is_related<T, U> : std::is_same<Bare<T>, Bare<U>> {};
и Bare
есть
template <typename T>
using Bare = RemoveCv<RemoveReference<T>>;
RemoveCv
и RemoveReference
являются шаблонами псевдонимов для std::remove_cv
и std::remove_reference
соответственно.
Живая демонстрация
Ответ 2
Компилятор создает экземпляр шаблона конструктора в этой строке:
wrapper<int> w2(w1);
потому что тип w1
равен wrapper<int>&
, а правила разрешения перегрузки указывают, что точное совпадение предпочтительнее преобразования. Конструктор, который принимает const wrapper<Obj>&
, требует квалификации const
, а wrapper<Obj>&&
- это rvalue-reference, которая не может связываться с lvalues.
Обычно нестратегированные перегрузки являются предпочтительной мишенью, чем шаблонные (таким образом, в нормальной ситуации выбирается копировальный конструктор), но поскольку шаблон конструктора принимает универсальная ссылка, он может выводить тип как int
, создавая идеальное соответствие и поэтому выбирается, следовательно, ошибка при отправке аргумента.
В качестве исправления вы можете отключить идеальный конструктор пересылки через SFINAE в определенных контекстах, как описано в в этой статье и @Преторианский ответ.
Ответ 3
Для меня работала более гранулированная версия примера Praetorian. Я определил что-то вроде is_compat<T, Arg>
, а затем передал это в выражение std::enable_if<>
(используя std::decay<>
, чтобы упростить сопоставление).
РЕДАКТИРОВАТЬ: найдено std::is_convertible
, MUCH simpleer.
Самостоятельный пример:
Пример строки:
#include <type_traits>
// Syntactic sugar
using std::enable_if;
using std::is_convertible;
template<bool Expr, typename Result = void>
using enable_if_t = typename enable_if<Expr, Result>::type;
template<typename From, typename To>
using enable_if_convertible_t = enable_if_t<is_convertible<From, To>::value>;
Затем вы можете делать перегрузки, например:
template<typename ... Args>
void my_func(Args&& ... args) {
cout << "1. my_func<Args...>(" << name_trait_list<Args&&...>::join() << ")" << endl;
}
// Use template with enable_if to catch as many types as possible
template<typename T1,
typename = enable_if_convertible_t<T1, string>>
void my_func(int y, T1&& z) {
cout
<< "2. my_func<T1:string>(int, " << name_trait<decltype(z)>::name()
<< ")" << endl;
}
// Example using multiple types (let compiler handle the combinatorics)
template<typename T1, typename T2,
typename = enable_if_t<is_convertible<T1, string>::value &&
is_convertible<T2, double>::value>>
void my_func(int y, T1&& z, T2&& zz) {
cout
<< "3. my_func<T1:string, T2:double>(int, "
<< name_trait<decltype(z)>::name() << ", "
<< name_trait<decltype(zz)>::name() << ")" << endl;
}
(ПРИМЕЧАНИЕ: name_trait*
- класс, испеченный дома)
Пример вывода:
>>> ( my_func(1, 2, 5, string("!!!")) );
1. my_func<Args...>(int&&, int&&, int&&, std::string&&)
>>> ( my_func(3, string("Hello")) );
2. my_func<T1:string>(int, std::string&&)
>>> ( my_func(4, (const string&)string("kinda")) );
2. my_func<T1:string>(int, const std::string&)
>>> ( my_func(5, "World") );
2. my_func<T1:string>(int, const char[6]&)
>>> ( my_func(6, var) );
2. my_func<T1:string>(int, char[6]&)
>>> ( my_func(7, var, 12) );
3. my_func<T1:string, T2:double>(int, char[6]&, int&&)
>>> ( my_func(9, var, 12.0) );
3. my_func<T1:string, T2:double>(int, char[6]&, double&&)