Подтвердить что ты не робот

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

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

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));

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

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

4b9b3361

Ответ 1

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

#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, чтобы оно принимало только обертки дескриптора файла.

Ответ 2

Я благодарю Говарда за своевременный и полезный ответ, моя проблема решена.

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

std::is_rvalue_reference<T>::value

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

!std::is_lvalue_reference<T>::value

Причина такова: мне нужна моя функция для получения "rvalue", а не "ссылки на rvalue". Функция SFINAE ed с std:: is_rvalue_reference:: value не получит "rvalue", но получает только "ссылку на rvalue". (выйти из причуды, да?)

Ответ 3

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

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

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


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

Один из способов сделать это может быть с С++ 17 и концепциями 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 в компилятор, поскольку он является расширением для С++, а не его основной частью.

Просто для полноты, здесь мой простой тест:

#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

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

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

std::is_rvalue_reference<T>::value

а не

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

Ответ 5

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

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

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

Anywho, вот что я использовал для отладки: (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 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;
}