Как я могу создать шаблонный конструктор, разрешающий все ссылки с l-значением, ссылки с r-значением и initializer_list?

#c #compiler-errors #c 11 #rvalue-reference #perfect-forwarding

#c #ошибки компилятора #c 11 #rvalue-ссылка #идеальная пересылка

Вопрос:

Я пытаюсь создать класс, который имеет два вектора больших последовательностей.

 std::vector<double> factory() {
    return std::vector<double>{1,2,3}; // it actually generates a large sequence of double
}

struct my_class {
    my_class(const std::vector<double>amp; x, const std::vector<double>amp; y)
     : m_x(x), m_y(y)
    { }

    std::vector<double> m_x;
    std::vector<double> m_y;
};

int main() {
    my_class c(factory(), factory());
    my_class c2(factory(), {0.5, 1, 1.5});
}
  

Ну, это работает хорошо, но он не использует конструктор перемещения vector. Итак, я попытался изменить конструктор, чтобы принимать ссылки на значения r с идеальной пересылкой.

 struct my_class {
    template<typename X, typename Y>
    my_class(Xamp;amp; x, Yamp;amp; y
             , typename std::enable_if<std::is_convertible<X, std::vector<double> >::value amp;amp;
                                       std::is_convertible<Y, std::vector<double> >::value>::type * = 0
            )
     : m_x(std::forward<X>(x)), m_y(std::forward<Y>(y))
    { }

    std::vector<double> m_x;
    std::vector<double> m_y;
};
  

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

 $ g   -W -Wall -std=gnu  0x a.cpp
a.cpp: In function ‘int main()’:
a.cpp:34:32: error: no matching function for call to ‘my_class::my_class(std::vector<double>, <brace-enclosed initializer list>)’
a.cpp:17:18: note: candidate is: my_class::my_class(const my_classamp;)
  

Я думал, что это std::initializer_list<double> может быть не конвертируемо в std::vector<double> , но на самом деле это конвертируемо, и я получил ту же ошибку, когда попытался без аргумента enable_if. Я что-то упускаю?

Комментарии:

1. Из любопытства, какую версию g вы используете? IIRC initializer_list поддержка была неполной в нескольких последних версиях.

2. @awoodland Я использую gcc 4.6.2. Что вы имеете в виду под ‘imcomplete’?

Ответ №1:

Предпочтительная идиома — передавать по значению, а затем вручную перемещать внутри списка инициализаторов элементов:

 struct my_class {
    my_class(std::vector<double> x, std::vector<double> y)
     : m_x(std::move(x)), m_y(std::move(y))
    { }

    std::vector<double> m_x;
    std::vector<double> m_y;
};
  

Это будет работать со всеми возможными аргументами и будет достаточно быстрым:

  • Если вы передадите вектор lvalue, вектор будет скопирован в x , а затем перемещен в m_x .
  • Если вы передадите вектор rvalue, вектор будет перемещен в x , а затем снова перемещен в m_x .
  • Если вы передадите список инициализаторов, x он будет инициализирован из этого списка, а затем перемещен в m_x .

Альтернативой является идеальная пересылка, но это затрудняет клиенту понимание того, что он может передать:

 struct my_class {
    template<typename T, typename U>
    my_class(Tamp;amp; x, Uamp;amp; y)
     : m_x(std::forward<T>(x)), m_y(std::forward<U>(y))
    { }

    std::vector<double> m_x;
    std::vector<double> m_y;
};
  

Кроме того, я получаю кучу предупреждений в g , поэтому я бы не рекомендовал это. Просто упомяну это для полноты картины.

Комментарии:

1. Передача по значению сгенерирует копию аргументов, чего именно хочет избежать пользователь1030861, если я правильно понимаю вопрос.

2. @Gabriel: Это зависит от категории значений аргумента. Если аргумент является значением rvalue, вызов по значению будет перемещать аргумент, а не копировать. Копирование выполняется только в том случае, если аргументом является значение lvalue.

3. @Gabrial: Обновил мой ответ более подробным объяснением. Помогает ли это?

4. Ваш второй пример кода не генерирует никаких предупреждений, если он скомпилирован как g -Wall -std=c 0x (версия g 4.5.2).

5. @Tony: Предупреждения появляются, как только вы создаете экземпляр шаблона конструктора со списком инициализаторов, например my_class foo({1.0, 2.0, 3.0}, {4.0, 5.0, 6.0});