How to reuse the return type of a function call inside a requires expression?

How to reuse the return type of a function call inside a requires expression?


7

I’m writing a concept that checks if a type can be used in an expression that composes 2 functions:

template<typename T>
concept C = requires(T t) {
    f(g(t));
};

i.e., I want to check if for a given object t of type T, I can call g(t) and then use the resulting value as an argument to f. e.g.,

auto g(int)  -> float;   
auto g(char) -> double; 

void f(float);
void f(double) = delete;

static_assert(C<int>);       // g(int) returns a float, so f(float) is called.
static_assert(not C<char>);  // g(char) returns a double, but f(double) is deleted

This works just fine.

However, I want to split up the calls to f and g because 1) g may take additional arguments (that don’t depend on T) resulting in a verbose call, and 2) I might want to use the return type/value of g multiple times inside the concept, so I don’t want to repeat the call multiple times.

Naive attempts like the following

auto res = g(t);
f(res);

and

using ret_t = decltype(g(t));
f(ret_t{});

don’t work inside concepts.

Is there some way to achieve the splitting up of f(g(t)) in a way that doesn’t require writing g(t) inside the call to f?

3

  • 3

    Declare an argument of the return type of g(t) in the parameter list like template<typename T> concept C = requires(T t, decltype(g(t)) arg) { f(arg); }?

    – 康桓瑋

    11 hours ago


  • @康桓瑋 Hmm, that could work, although if g(t) is ill-formed, the concept fails in the parameters to the requires clause rather than in the body of the requires clause. Might not be a terrible option though.

    – cigien

    11 hours ago

  • @cigien even if it did fail in the parameters, such substitution failure in a concept simply means that the concept is unsatisfied, not that the program is ill-formed, so it's okay. Using decltype(g(t)) in the parameter list is by far the shortest solution.

    – Jan Schultke

    11 hours ago

1 Answer
1


6

A nested requirement can be an option:

template<typename T>
concept C = requires(T t) {
    g(t);   // Not strictly needed, I think, but explicit verification might be more readable if the concept fails in a template
    requires requires(decltype(g(t)) s) { // Can reuse s how many time we want
        f(s);
    };
};

And yes, requires requires is required here to make sure f(s) is verified as well.

1

  • 1

    Ah, that's interesting, I hadn't realized requirements could be nested quite like this. (And yes, I would definitely like the explicit verification of g(t)). I'll try it out, thanks.

    – cigien

    11 hours ago



Leave a Reply

Your email address will not be published. Required fields are marked *