Предотвращение разрешения неконстантных значений lvalue на ссылку rvalue вместо ссылки const lvalue

#c #templates #metaprogramming #rvalue-reference

#c #шаблоны #метапрограммирование #rvalue-reference

Вопрос:

У меня возникли проблемы с перегрузкой функции для получения значения либо по ссылке const, либо, если это значение rvalue, ссылка rvalue. Проблема в том, что мои неконстантные значения lvalue привязаны к версии функции rvalue. Я делаю это в VC2010.

 #include <iostream>
#include <vector>

using namespace std;

template <class T>
void foo(const Tamp; t)
{cout << "void foo(const Tamp;)" << endl;}

template <class T>
void foo(Tamp;amp; t)
{cout << "void foo(Tamp;amp;)" << endl;}

int main()
{
    vector<int> x;
    foo(x); // void foo(Tamp;amp;) ?????
    foo(vector<int>()); // void foo(Tamp;amp;)
}
  

Приоритет, по-видимому, заключается в том, чтобы вывести foo(x) как

 foo< vector<int> amp; >(vector<int>amp; amp;amp; t)
  

вместо

 foo< vector<int> >(const vector<int>amp; t)
  

Я попытался заменить версию rvalue-reference на

 void foo(typename remove_reference<T>::typeamp;amp; t)
  

но это привело только к тому, что все разрешилось до ссылочной версии const-lvalue.

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

РЕДАКТИРОВАТЬ: просто добавлены не шаблонные версии функций, и они работают, как ожидалось. Превращение функции в шаблон изменяет правила разрешения перегрузки? То есть .. действительно расстраивает!

 void bar(const vector<int>amp; t)
{cout << "void bar(const vector<int>amp;)" << endl;}

void bar(vector<int>amp;amp; t)
{cout << "void bar(vector<int>amp;amp;)" << endl;}

bar(x); // void bar(const vector<int>amp;)
bar(vector<int>()); // void bar(vector<int>amp;amp;)
  

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

1. Что происходит, когда вы создаете x const? Что происходит, когда вы используете x после другого вызова foo ?

2. Можно организовать, чтобы выведенный Tamp;amp; привязывался только к rvalues . См . ideone.com/XSBifq . Но я бы не рекомендовал это. Принятый ответ, т. Е. Не использовать подобную перегрузку, является правильным подходом.

3. channel9.msdn.com/Shows/Going Deep/…

Ответ №1:

Когда у вас есть шаблонная функция, подобная этой, вы почти никогда не захотите перегружать. Tamp;amp; Параметр является параметром catch anything. И вы можете использовать это, чтобы получить любое поведение, которое вы хотите, из одной перегрузки.

 #include <iostream>
#include <vector>

using namespace std;

template <class T>
void display()
{
    typedef typename remove_reference<T>::type Tr;
    typedef typename remove_cv<Tr>::type Trcv;
    if (is_const<Tr>::value)
        cout << "const ";
    if (is_volatile<Tr>::value)
        cout << "volatile ";
    std::cout << typeid(Trcv).name();
    if (is_lvalue_reference<T>::value)
        std::cout << 'amp;';
    else if (is_rvalue_reference<T>::value)
        std::cout << "amp;amp;";
    std::cout << 'n';
}

template <class T>
void foo(Tamp;amp; t)
{
    display<T>();
}

int main()
{
    vector<int> x;
    vector<int> const cx;
    foo(x); // vector<int>amp;
    foo(vector<int>()); // vector<int>
    foo(cx);  // const vector<int>amp;
}
  

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

1. Да, вы поняли. Хотя я бы сформулировал это по-другому: amp;amp; в не шаблонной функции означает «семантику перемещения», amp;amp; в шаблонной функции означает «идеальную пересылку»

2. Я думаю, достаточно справедливо, но это меня немного расстраивает. Я вижу проблемы, возникающие из-за отсутствия единства между семантикой.

3. Достаточно справедливо. Я и некоторые другие ребята (Питер и Дейв) пытались убить двух зайцев одним выстрелом с введением ссылок на значения rvalue: переместить семантику и совершенную пересылку. В то время (2002) мы думали, что минимальное изменение языка с минимальной обратной несовместимостью или вообще без нее и выполнение более одной невыполнимой задачи было бы хорошо. Но если бы мы проектировали с чистого листа, безусловно, более чистые решения кажутся правдоподобными. Конечно, если это разочарование усилится, следует ограничиться подмножеством C 98/03, и, таким образом, эта проблема не возникнет.

4. <вздох> <не по теме> и мои искренние соболезнования семье Денниса Ричи.

5. Ну, да, но это довольно плохой вариант. Это просто означает, что вы должны помнить, что нужно быть очень осторожным при попытке шаблонизировать любую функцию ссылками на rvalue. К сожалению, если вы забудете, что обычно он все равно будет компилироваться нормально, за исключением того, что вы уничтожите все объекты, с которыми вы вызываете эту функцию! Я легко мог видеть, что это приводит к незаметным и труднодоступным ошибкам, особенно если вы привыкли к шаблонам C 03, которые всегда генерировали эквивалентные функции.

Ответ №2:

Для Tamp;amp; привязки к ссылке lvalue T сам должен быть ссылочным типом lvalue . Вы можете запретить создание экземпляра шаблона со ссылочным типом T :

 template <typename T>
typename std::enable_if<!std::is_reference<T>::value>::type foo(Tamp;amp; t)
{
    cout << "void foo(Tamp;amp;)" << endl;
}
  

enable_if находится в <utility> ; is_reference находится в <type_traits> .

Причина, по которой перегрузка Tamp;amp; предпочтительнее перегрузки, принимающей a T constamp; , заключается в том, что это Tamp;amp; точное совпадение (с T = vector<int>amp; ), но T constamp; требует преобразования квалификации (необходимо добавить const-квалификацию).

Это происходит только с шаблонами. Если у вас есть нестандартная функция, которая принимает a std::vector<int>amp;amp; , вы сможете вызвать эту функцию только с аргументом rvalue . Когда у вас есть шаблон, который принимает a Tamp;amp; , вы не должны думать о нем как о «параметре ссылки rvalue»; это «универсальный параметр ссылки» (я полагаю, Скотт Мейерс использовал аналогичный язык). Он может принимать что угодно.

Разрешение Tamp;amp; параметру шаблона функции привязываться к любой категории аргументов — это то, что обеспечивает идеальную пересылку.

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

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

2. @Ayjay Нет, обычно достаточно одного шаблона, который использует идеальную пересылку. Поскольку он принимает что угодно (см. Ответ Говарда), а категория значения, а также квалификаторы cv аргумента сохраняются, вам редко требуется другая перегрузка.

3. Я пытался это сделать, и проблема, которую я нахожу, заключается в том, что бороться с ошибками компиляции сложно. Несмотря на то, что фактический код, который будет выполняться, синтаксически корректен, некоторые другие условия if имеют недопустимый синтаксис. Это меня расстраивает, и я могу использовать enable-if для восстановления не шаблонных правил перегрузки значений rvalue, чтобы я мог писать функции так же, как если бы они не были шаблонными.