janekb04
janekb04

Reputation: 4925

How do you take a forwarding reference to a specific type?

When writing a thread-safe std::stack wrapper I made the following two overloads for push:

void push(const value_type& value)
{
    auto guard = std::scoped_lock{_mutex};
    _stack.push(value);
    _cv.notify_one();
}

void push(value_type&& value)
{
    auto guard = std::scoped_lock{_mutex};
    _stack.push(std::move(value));
    _cv.notify_one();
}

They are nearly the same except that one takes a const lvalue reference and one takes an rvalue reference. Normally I would use perfect forwarding to deal with this (now using the glorious C++20 abbreviated template declaration):

void push(auto&& value)
{
    auto guard = std::scoped_lock{_mutex};
    _stack.push(std::forward<decltype(value)>(value));
    _cv.notify_one();
}

The problem here is that this accepts any type, while it should accept only value_type and references to it.

Is there some standard way to solve this? So far I came up with two approaches. Either use a std::enable_if to somehow check if the template type is value_type or a reference to it, or use a concept.

Upvotes: 4

Views: 947

Answers (2)

Hunter Kohler
Hunter Kohler

Reputation: 2715

I repeat @Ayxan Haqverdili's example in multiple versions. Also note, because you mention that you want a standard way to solve this, that C++20 concepts are the standard way now. Unless you are writing code that requires compatibility with old language versions, that should be used. The primary reason is that it clearly and concisely documents the requirements of the function at the declaration site. This is even more eloquent than static_assert, and allows overloading instead of direct failure. std::enable_if is simply the old method of instating requirement.

An Inline Concept

template <class T>
    requires std::convertible_to<T, value_type>
void push(T &&value);

A Custom Concept

This lets you use the convenient parameter auto.

template <class T, class U>
concept ForwardType = std::convertible_to<T, U>

void push(ForwardType<value_type> auto &&value);

Using std::enable_if

This should be compatible with C++11.

template <class T>
std::enable_if_t<std::is_convertible_v<T>> push(T &&value);

Upvotes: 3

Aykhan Hagverdili
Aykhan Hagverdili

Reputation: 29975

You can assert it:

template<typename T>
void push(T&& value)
{
    static_assert(is_same_v<remove_reference_t<T>, value_type>);
    // ...
}

You could alternatively use is_convertible instead of is_same so it works more naturally.

Upvotes: 4

Related Questions