Как сделать так, чтобы параметр ссылки на rvalue шаблона ТОЛЬКО связывался с ссылкой на rvalue? - PullRequest
29 голосов
/ 23 октября 2011

Я пишу сетевую библиотеку и интенсивно использую семантику перемещения для обработки владения файловыми дескрипторами. Один из моих учеников хочет получить обертки дескрипторов файлов других типов и стать владельцем, так что это что-то вроде

struct OwnershipReceiver
{
  template <typename T>
  void receive_ownership(T&& t)
  {
     // taking file descriptor of t, and clear t
  }
};

Он должен иметь дело с несколькими несвязанными типами, так что receive_ownership должен быть шаблоном, и, чтобы быть в безопасности, я хочу, чтобы он ТОЛЬКО связывался со ссылками rvalue, чтобы пользователь мог явно указывать std :: move при передаче lvalue.

receive_ownership(std::move(some_lvalue));

Но проблема в том, что дедукция шаблона C ++ позволяет передавать lvalue без лишних усилий. И я действительно однажды выстрелил себе в ногу, случайно передав lvalue в receive_ownership и позже использовав это lvalue (очищенное).

Итак, вот вопрос: как сделать шаблон ТОЛЬКО привязанным к rvalue-ссылке?

Ответы [ 5 ]

32 голосов
/ 23 октября 2011

Вы можете ограничить T, чтобы он не был ссылкой на lvalue, и, таким образом, запретить привязку lvalue к нему:

#include <type_traits>

struct OwnershipReceiver
{
  template <typename T,
            class = typename std::enable_if
            <
                !std::is_lvalue_reference<T>::value
            >::type
           >
  void receive_ownership(T&& t)
  {
     // taking file descriptor of t, and clear t
  }
};

Также было бы неплохо добавить какое-то ограничение к T, чтобы оно принимало только оболочки файловых дескрипторов.

8 голосов
/ 03 октября 2016

Простой способ - предоставить удаленный элемент , который принимает ссылку lvalue:

template<typename T> void receive_ownership(T&) = delete;

Это всегда будет лучшим соответствием для аргумента lvalue.


Если у вас есть функция, которая принимает несколько аргументов, все из которых должны быть значениями r, нам понадобится несколько удаленных функций. В этой ситуации мы можем предпочесть использовать SFINAE, чтобы скрыть функцию от любых аргументов lvalue.

Одним из способов сделать это может быть C ++ 17 и Concepts TS:

#include <type_traits>

template<typename T>
void receive_ownership(T&& t)
    requires !std::is_lvalue_reference<T>::value
{
     // taking file descriptor of t, and clear t
}

или

#include <type_traits>

void receive_ownership(auto&& t)
    requires std::is_rvalue_reference<decltype(t)>::value
{
     // taking file descriptor of t, and clear t
}

Пройдя немного дальше, вы сможете определить собственную новую концепцию, которая может быть полезна, если вы хотите использовать ее повторно, или просто для большей ясности:

#include <type_traits>

template<typename T>
concept bool rvalue = std::is_rvalue_reference<T&&>::value;


void receive_ownership(rvalue&& t)
{
     // taking file descriptor of t, and clear t
}

Примечание: в GCC 6.1 вам необходимо передать -fconcepts компилятору, так как это расширение C ++ 17, а не его основная часть.

Просто для полноты вот мой простой тест:

#include <utility>
int main()
{
    int a = 0;
    receive_ownership(a);       // error
    receive_ownership(std::move(a)); // okay

    const int b = 0;
    receive_ownership(b);       // error
    receive_ownership(std::move(b)); // allowed - but unwise
}
4 голосов
/ 23 октября 2011

Я узнал кое-что, что, кажется, смущает людей довольно часто: использование SFINAE - это нормально, но я не могу использовать:

std::is_rvalue_reference<T>::value

Единственный способ, которым я хочу, это

!std::is_lvalue_reference<T>::value

Причина: мне нужна моя функция для получения значения r , а не значения r ссылка . Функция, условно включенная с помощью std::is_rvalue_reference<T>::value, не получит значение r, а скорее получит ссылку на значение r.

2 голосов
/ 30 сентября 2016

Для ссылок lvalue, T выводится как ссылка lvalue, а для ссылок rvalue, T выводится как не ссылка.

Таким образом, если функция привязывается к rvalue-ссылке, то, что в конце видит компилятор для определенного типа T:

std::is_rvalue_reference<T>::value

а не

std::is_rvalue_reference<T&&>::value

0 голосов
/ 05 января 2018

К сожалению, похоже, что попытка is_rvalue_reference<TF> (где TF - это идеально перенаправленный тип) не работает хорошо, если вы на самом деле пытаетесь создать перегрузки, которые различают const T& и T&&(например, с использованием enable_if в обоих, один с is_rvalue_reference_v<TF>, а другой с !is_rvalue_reference_V<TF>).

Решение (пусть и хакерское) состоит в том, чтобы убрать переадресованный T, а затем поместить перегрузки вКонтейнер знает об этих типах.Сгенерировал этот пример :

Хуп, я ошибся, просто забыл посмотреть на ответ Тоби (is_rvalue_reference<TF&&>) - хотя сбивает с толку, что вы можете сделать std::forward<TF>(...),но я думаю, именно поэтому decltype(arg) также работает.

В любом случае, вот что я использовал для отладки: (1) использование перегрузок struct, (2) использование неправильной проверки для is_rvalue_reference и (3) правильная проверка:

/*
Output:

const T& (struct)
const T& (sfinae)
const T& (sfinae bad)
---
const T& (struct)
const T& (sfinae)
const T& (sfinae bad)
---
T&& (struct)
T&& (sfinae)
const T& (sfinae bad)
---
T&& (struct)
T&& (sfinae)
const T& (sfinae bad)
---
*/

#include <iostream>
#include <type_traits>

using namespace std;

struct Value {};

template <typename T>
struct greedy_struct {
  static void run(const T&) {
    cout << "const T& (struct)" << endl;
  }
  static void run(T&&) {
    cout << "T&& (struct)" << endl;
  }
};

// Per Toby's answer.
template <typename T>
void greedy_sfinae(const T&) {
  cout << "const T& (sfinae)" << endl;
}

template <
    typename T,
    typename = std::enable_if_t<std::is_rvalue_reference<T&&>::value>>
void greedy_sfinae(T&&) {
  cout << "T&& (sfinae)" << endl;
}

// Bad.
template <typename T>
void greedy_sfinae_bad(const T&) {
  cout << "const T& (sfinae bad)" << endl;
}

template <
    typename T,
    typename = std::enable_if_t<std::is_rvalue_reference<T>::value>>
void greedy_sfinae_bad(T&&) {
  cout << "T&& (sfinae bad)" << endl;
}

template <typename TF>
void greedy(TF&& value) {
  using T = std::decay_t<TF>;
  greedy_struct<T>::run(std::forward<TF>(value));
  greedy_sfinae(std::forward<TF>(value));
  greedy_sfinae_bad(std::forward<TF>(value));
  cout << "---" << endl;
}

int main() {
  Value x;
  const Value y;

  greedy(x);
  greedy(y);
  greedy(Value{});
  greedy(std::move(x));

  return 0;
}
...