2014-01-27 4 views
2

Вот класс с вариационным конструктором и его специализация для копирования и перемещения из временного.Специализация шаблона конструктора 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' 

Как решить эту проблему?

ответ

6

Вас укусит жадный идеальный конструктор переадресации.

wrapper<int> w2(w1); 

В приведенном выше линии, совершенствуя экспедиторская конструктор лучше подходит по сравнению с конструктором копирования, так как Args выводится в wrapper<int>&.

решение быстро исправить А изменить строку выше

wrapper<int> w2(static_cast<wrapper<int> const&>(w1)); 

это правильно вызывает конструктор копирования, но, помимо того, что излишне многословен, не решает основную проблему.

Для решения исходной проблемы необходимо условно отключить идеальный конструктор переадресации, если Args - это то же самое, что и wrapper<Obj>.

Here's отличное сообщение в блоге, описывающее проблему, и как ее решить.Подводя итог, необходимо изменить совершенное определение экспедиторского конструктора для

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 соответственно.

Live demo

+0

Не должно быть 'is_related , Args >> ...'? Кроме того, почему это должно быть отключено, если типы одинаковы? – 0x499602D2

+0

@ 0x499602D2 Нет, 'Args' необходимо развернуть в' is_related', потому что вы хотите, чтобы основной шаблон 'is_related' был выбран, когда' sizeof ... (Args)! = 1'. Вы хотите отключить его, когда типы совпадают, потому что в этом случае вы хотите, чтобы конструктор copy/move был выбран вместо совершенной пересылки. – Praetorian

+0

О, я не заметил, что в основном шаблоне был пакет шаблонов.Во всяком случае, вы говорите, что если вы делаете 'wrapper w (x, y, z)' где '{x, y, z}' все типа 'Obj' (что означает' int'), то шаблон конструктора * должен 't * быть выбрано? Я предполагаю, что вы думаете, что они не должны, потому что вы используете 'DisableIf'. Но я думаю, что это должно быть 'EnableIf'. – 0x499602D2

3

Компилятор инстанцирование шаблона конструктора по этой линии:

wrapper<int> w2(w1); 

, так как тип w1 является wrapper<int>& и правила разрешения перегрузки dicate, что точное совпадение предпочтительнее преобразование. Конструктор, который принимает const wrapper<Obj>&, требует квалификации const, а wrapper<Obj>&& - это rvalue-reference, который не может связываться с lvalues.

Обычно нешаблонные перегрузки являются предпочтительной мишенью, чем те, шаблона (таким образом, в нормальной ситуации копии-конструктор будет выбираться), но так как шаблон конструктора принимает universal reference, он может вывести тип как int решений идеальное совпадение и поэтому выбрано, следовательно, ошибка при отправке аргумента.

В качестве исправления вы можете отключить идеальный конструктор пересылки через SFINAE в определенных контекстах, как описано в this article, и на @Praetorian's answer.

+0

Это немного резкое решение, не так ли? Идеальные конструкторы пересылки очень удобны при создании таких оберток. Вы можете отключить их с помощью SFINAE, чтобы предотвратить эту проблему. Р. Мартиньо Фернандес описывает, как это сделать в [this] (http://flamingdangerzone.com/cxx11/2012/06/05/is_related.html) сообщении. – Praetorian

+0

@Praetorian Интересно! Хорошо, я изменю его. :) – 0x499602D2

+0

Nit-pick: привязка выражения к 'const wrapper &' непосредственно (т. Е. Без создания временного) по-прежнему занимает Точное соответствие. В [over.ics.rank]/3 есть специальное правило, которое устраняет проблему. Точно так же функции без шаблонов предпочтительнее только над шаблонами функций, если все преобразования аргументов имеют одинаковый «ранг» (ни один не хуже, ранг является частью этого) [over.match.best]/1. – dyp

1

Для меня, используя более зернистую версию примера преторианской работал для меня. Я определил что-то вроде is_compat<T, Arg>, а затем подайте это в выражение std::enable_if<> (используя std::decay<>, чтобы упростить сопоставление).

EDIT: Найдено std::is_convertible, MUCH simpler.

Автономные Пример:

Инлайн Пример:

#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&&) 
+1

. В качестве альтернативы, вместо того, чтобы иметь несколько типов шаблонов, вы можете попробовать комбинировать логику с помощью' && ', что-то вроде' enable_if_t && is_compat_v '. – eacousineau

+0

Нет, не может, по крайней мере C++ 1y. Моя попытка: [diff для tpl_spec_greedy.cc] (https://github.com/EricCousineau-TRI/repro/commit/b425bac). – eacousineau

+0

Второе обновление: да, вы можете; Я просто делал что-то неправильно с шаблонами 'using'. – eacousineau

Смежные вопросы