Специализация шаблона конструктора 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&&)