1

I have a class template that take a variadic number of template arguments. The class has some functions that take arguments corresponding to the class's template parameters. I would like these functions to bind to both lvalues and rvalues. How can I achieve this?

template<class ...C>
class Graph {
public:
    Graph(C &...Context); // <- Only lvalues
    Graph(C &&...Context); // <- Only rvalues
    Graph(auto &&...Context); // <- Both lvalues and rvalues, but does not enforce that the types correspond to ...C
};

It doesn't seem to be possible to create a concept inside the class. Can I create a concept outside the class and pass both the class template pack and the function template pack and verify them in some way? Or can I can add a requires clause to the function that can solve it?

I have found some similar questions, but they are quite old, so there are no answers using concepts.

1
  • Is the end goal here to copy lvalues and move rvalues? In that case simply taking all parameters by value can many times be a valid option that simplifies your code. The only loss with that approach AFAIK is that you sometimes get an extra move operation. Commented Jun 26, 2023 at 6:48

2 Answers 2

4

Concept std::constructible_from might help:

template <class... C>
class Graph {
public:
    template <typename... Ts>
    requires((std::constructible_from<C, Ts> && ...))
    Graph(Ts&&...);
};

Demo

or more strict std::convertible_to

template <class... C>
class Graph {
public:
    template <typename... Ts>
    requires((std::convertible_to<Ts, C> && ...))
    Graph(Ts&&...);
};

Demo

or std::same_as:

template <class... C>
class Graph {
public:
    template <typename... Ts>
    requires(((std::same_as<Ts&&, C&&>
               || (std::same_as<Ts&&, C&> /*&& !std::is_rvalue_reference_v<C>*/))
            && ...))
    Graph(Ts&&...);
};

Demo

Sign up to request clarification or add additional context in comments.

8 Comments

Thanks. Is the && in Ts&& needed? Also, this allows types that can be created from each other. For example, struct A { int i; }; Graph<A>(42); would be allowed. How can I make it stricter?
Extra && removed, added another concept more restrictive.(even more restrictive would be with std::same_as)
Could you give an example with same_as as well? Just exchanging constructible_from with same_as does not work.
same_as version added. Unsure if you want to accept rvalue when C contains (l/rvalue) reference...
Thanks. I'd like to it to work exactly like f(auto &&...), just with a constraint on the types allowed by auto. The same_as version does not seem to work correctly with const and volatile arguments: godbolt.org/z/acq6EhsfW
|
1

It sounds like what you want is

template<class... C>
class Graph {
public:
    template<class... Ts>
    requires (std::same_as<Ts&, C&> and ...)
    Graph(Ts&&... Context);
};

This exploits reference collapsing rules for concision.

If you want, you can write a concept for this (here written out in full):

template<class T, class U>
concept same_as_or_lvalue_reference_to = std::same_as<T, U> or std::same_as<T, U&>;

template<class... C>
class Graph {
public:
    Graph(same_as_or_lvalue_reference_to<C> auto&&... Context);
};

3 Comments

Thanks! I cannot get the concept solution to work: wrong number of template arguments (1, should be 2).
The requires solution is very nice, but it does not work with const: Graph<const int>(42);.
Fixed the concept, was missing an auto. You can fix it for const by using std::remove_const_t and std::remove_cvref_t (not as elegant, though).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.