2016-03-19 6 views
-1

Пожалуйста, обратите внимание следующее tree классаКонструктор дизайна для класса, который делегирует значение одной из переменных членов

template<typename T> 
class tree 
{ 
public: 
    template<typename U> 
    tree(U&& value) 
     : m_value(std::forward<U>(value)) 
    { } 

private: 
    T m_value; 
}; 

и некоторый многословный класс foo который строится его std::string name, который печатается через std::cout всякий раз, когда один из его конструкторы вызываются.

foo a("a"), b("b"); 
foo const c("c"); 
tree<foo> s(std::move(a)), t(b), u(c); 

дает следующий результат:

a constructed 
b constructed 
c constructed 
a moved 
b copied 
c copied 

, которое, как ожидалось.

Как это работает?

Чтобы быть конкретным: Мы не можем использовать

tree(T&& value) 
    : m_value(std::forward<T>(value)) 
{ } 

Почему? Ну, потому что мы вынуждаем decltype(value) быть foo&&, которого нет в t(b) и u(c). Нам нужно будет предоставить

tree(T const& value) 
    : m_value(value) 
{ } 

также, если мы хотим использовать этот спорный вариант. Ctor, используемый в работе по внедрению tree, является причиной reference collapsing rules.

Но почему мы не можем использовать

template<typename U> 
tree(U&& value) 
    : m_value(std::forward<T>(value)) 
{ } 

вместо этого?

Ну, причина в том, что в u(c) мы имеем U = foo const& = decltype(value), но std::forward<T> принимает только foo& или foo&&, так как T = foo. Напротив, std::forward<U> принимает foo const& (или foo const&&), начиная с U = foo const&.

Так что, если я не с видом что-то, что мы должны использовать template<typename U> tree(U&&) вместо tree(T const&), tree(T&&) комбинации. Но: компилятор примет template<typename U> tree(U&&) CTOR в push_back, где намерены в том, что движение к т е р о tree принимается:

foo a("a"), b("b"); 
foo const c("c"); 

tree<foo> t("root"); 
t.emplace_back(std::move(a)); 
t.emplace_back(b); 
t.emplace_back(c); 

tree<foo> u("other root"); 
u.push_back(t); // cannot convert argument 1 from 'tree<foo>' to 'std::string const&' 

Что я могу сделать? Нужно ли вместо этого использовать комбинацию tree(T const&), tree(T&&)?

[Извините за слишком многословным, но я чувствовал себя ответственным за прояснении любой технический вопрос, прежде чем просить для дизайна.]

ответ

0

Вы можете сделать оба.

Если вы идете с:

template<typename U> 
tree(U&& value) 
    : m_value(std::forward<U>(value)) 
{ } 

недостатком является то, что любой из аргументов конструктор T может быть вызван таким образом. Возможно, это не то, что вы хотите. Например, с помощью этого варианта, справедливо следующее:

struct foo 
{ 
    foo() = default; 
    foo(const foo &ref) = default; 
    explicit foo(int); 
}; 

tree<foo> t(10); 
t = 20; 

Это решение, которое вы должны найти для себя, однако, лично я вижу, что, как огромный вниз. Я бы сделал этот конструктор explicit (исключая вторую инициализацию) и перейдите на tree(const T&) вместе с tree(T&&), чтобы избежать первой инициализации.

+0

Мы можем объявить 'template tree (U &&)' '' '' 'явным'. Тогда «t = 20» было бы невозможно. – 0xbadf00d

+0

@ 0xbadf00d Вот что я написал в своем последнем абзаце, да. Хотя, вероятно, это не так понятно. –

1

Жадный конструктор, такой как template<typename U> tree(U&&), должен быть ограниченно ограничен, или у вас будет много проблем в будущем. Вы видели, что это построение копии с недопустимым значением, потому что оно лучше, чем конструктор копирования. Он также определяет неявное преобразование из всего под солнцем, которое может иметь «забавные» эффекты при разрешении перегрузки.

Возможное ограничение может быть «принимать только вещи, которые могут быть конвертированы в T»:

template<typename U, class = std::enable_if_t<std::is_convertible_v<U, T>>> 
tree(U&& u) : m_value(std::forward<U>(u)) {} 

Или, возможно, „принимать только вещи, которые могут быть конвертированы в T и не tree“.

template<class U> 
using not_me = std::negation<std::is_same<U, tree>>; 

template<typename U, 
     class = std::enable_if_t<std::conjunction_v<not_me<std::decay_t<U>>, 
                std::is_convertible<U, T>>>> 
tree(U&& u) : m_value(std::forward<U>(u)) {} 
+0

Почему конвертируемость в 'T' важна? – 0xbadf00d

+0

@ 0xbadf00d Так что вы не определяете неявные преобразования, которые не работают. –

+0

Но когда они не работают, они все равно генерируют ошибку компилятора. Что мне не хватает? – 0xbadf00d

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

  • Нет связанных вопросов^_^