Is enable_if the most concise way to define a function accepting only rvalues, but of any type?
Asked Answered
C

3

6

I'm referring to this:

#include <utility>

template<typename T, typename = std::enable_if_t<std::is_rvalue_reference_v<T&&>>>
auto f(T&&) {}

int main(){
    int i{};
    f(std::move(i)); // ok
    f(int{1});       // ok
    f(i);            // compile time error, as expected
}

Are there any other, shorter ways to accomplish the same?

For a moment I thought something like this could work

template<typename T>
auto f(decltype(std::declval<T>())&&) {}

but the IDE told me couldn't infer template argument 'T', and I verified here, in the section Non-deduced contexts, that the expression of a decltype-specifier is indeed a non-deduced context.


I'm interested also in a solution, if any exists.

Carrousel answered 4/7, 2022 at 15:31 Comment(10)
Use concepts: template <std::rvalue_reference T> auto T(T&&) { }. Simplified everywhere you can have a typename you can have a concept instead of enable_ifHenriettahenriette
Thanks, probably I should have added C++17 as well. Doing it now, but removing C++23, as given C++20 has concepts, I don't expect C++23 to offer an even shorter syntax.Carrousel
C++17? Bummer. I think you already have the solution for c++17. You need concepts to make this nicer.Henriettahenriette
@GoswinvonBrederlow, apparently the solution was more obvious than we thought.Carrousel
@GoswinvonBrederlow I don't think std::rvalue_reference is a thing. :cSubstage
I'd spell std::is_rvalue_reference_v<T&&> as !std::is_reference_v<T>, and use requires if this was C++20. Otherwise there's not much to change. I'm not a fan of the second overload, since it requires you to duplicate the function type.Substage
By the way, it's better to use the enable_if on the return type, that way a user can't accidentally put int n; f<int, bool>(n), which overrides the type check.Credulous
@GoswinvonBrederlow This is doubly wrong. is_rvalue_reference_v (which is the actual spelling) is a variable template, not a concept, so you can't use it in this context. But even if you could, T doesn't deduce as an rvalue reference in the rvalue case - so that constraint would reject everything (f(42) deduces T=int not T=int&&, which is why OP checked if T&& was an rvalue reference type, not T).Ullrich
@Ullrich I didn't use is_rvalue_reference_v for a reason. The type traits use the form is_something with is_something_t for the type and is_something_v for the value., The corresponding concept is then generally named something or Something. The concept might not exists (yet?) in this case but if it did I would expect it to require that T&& is an rvalue reference and therefore work for the deduced T.Henriettahenriette
@GoswinvonBrederlow If rvalue_reference<T> existed as a concept, then surely rvalue_reference<int> would be false because int is not an rvalue reference.Ullrich
I
3

As @HolyBlackCat commented, you can use concepts to simplify the function signature

#include <type_traits>

template<typename T>
  requires (!std::is_lvalue_reference_v<T>)
auto f(T&&) {}

Or detect lvalue or rvalue by checking the validity of a lambda expression that accepts an lvalue reference

#include <utility>

template<typename T>
  requires (!requires (T&& x) { [](auto&){}(std::forward<T>(x)); })
auto f(T&&) {}
Inanimate answered 4/7, 2022 at 16:5 Comment(2)
This should be the acceptable answer over others, as this is the much less typing c++20 version.Waste
@Waste "much less typing" could perhaps be a reason if it was true. The top requires in this answer is 41 chars. The overload solution iin paolo's answer is 43 chars and the static_assert I used in my answer is 47 chars. So, the span is 41-47. I wouldn't select a solution based on that small difference.Atop
S
8

Did you try explicitly deleting the l-value overload?

template <typename T>
auto f(T &&) {}

template <typename T>
auto f(T &) = delete;
Stelliform answered 4/7, 2022 at 15:39 Comment(3)
Since this seems to be preferred over only having one function template and using a static_assert, I wonder, how come? I'm a bit surprised :-)Atop
@TedLyngmo If you ask me: it delivers intent clearly and it doesn't require you to deal with type traits (nor to #include <type_traits>).Stelliform
That's true and I do like ithis solution. I do like the static_assert better though since it doesn't require overloading the function and "use of deleted function 'auto f(T&) [with T = ...]'" doesn't say as much as static assertion failed ... static_assert(std::is_rvalue_reference_v<T&&>, "f() requires an r-value"); (if the type trait isn't obvious enough). To me it seems static_assert was invented partly to be able to make error messages in situations like this clearer. Oh well, it's a matter of taste I guess.Atop
I
3

As @HolyBlackCat commented, you can use concepts to simplify the function signature

#include <type_traits>

template<typename T>
  requires (!std::is_lvalue_reference_v<T>)
auto f(T&&) {}

Or detect lvalue or rvalue by checking the validity of a lambda expression that accepts an lvalue reference

#include <utility>

template<typename T>
  requires (!requires (T&& x) { [](auto&){}(std::forward<T>(x)); })
auto f(T&&) {}
Inanimate answered 4/7, 2022 at 16:5 Comment(2)
This should be the acceptable answer over others, as this is the much less typing c++20 version.Waste
@Waste "much less typing" could perhaps be a reason if it was true. The top requires in this answer is 41 chars. The overload solution iin paolo's answer is 43 chars and the static_assert I used in my answer is 47 chars. So, the span is 41-47. I wouldn't select a solution based on that small difference.Atop
A
1

You can use static_assert if you don't need SFINAE:

template<typename T>
auto f(T&&) {
    static_assert(std::is_rvalue_reference_v<T&&>);
}
Atop answered 4/7, 2022 at 16:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.