1

I continue my C++20 concept(ual) jourrney... I would like to simplify the following code by deducing the template parameter T from the predicate argument, so that the client code does not have to precise the type of T if it can be deduced from P1.

I guess it is possible, I just don't know the syntax: I tried various forms of template template + requires clause, but without having a successful compilation.

Any lead?

#include<concepts>
#include<utility>
#include<string>

template<class T, std::predicate<T> P1>
struct Foo
{
    P1 _f;
    Foo(P1 &&f): _f(std::forward<P1>(f)) {}
};

template<class T, std::predicate<T> P1>
auto make_foo(P1 &&f)
{
return Foo<T, P1>(std::forward<P1>(f));
}

int main()
{
    auto fun = [](const std::string &s){return s == "toto";};
    // auto my_foo = make_foo(fun); // candidate template ignored: couldn't infer template argument 'T'
    auto my_foo = make_foo<std::string>(fun);
    return 0;
}
WaterFox
  • 850
  • 6
  • 18

1 Answers1

1

std::function has good deduction guides to get the type of a callable:

// Replacement for `std::function<T(U)>::argument_type`
template<typename T> struct single_function_argument;
template<typename Ret, typename Arg> struct single_function_argument<std::function<Ret(Arg)>> { using type = Arg; };

// Deduction guide
template<class P1>
Foo(P1 &&) -> Foo<typename single_function_argument<decltype(std::function{std::declval<P1>()})>::type, P1>;

template<class P1>
auto make_foo(P1 &&f)
{
    // Use CTAD
    return Foo(std::forward<P1>(f));
}

// Can still specify type manually if you want
template<class T, std::predicate<T> P1>
auto make_foo(P1 &&f)
{
    return Foo<T, P1>(std::forward<P1>(f));
}

int main() {
    auto fun = [](const std::string &s){return s == "toto";};
    auto my_foo1 = Foo(fun);
    auto my_foo2 = make_foo(fun);
}

The reason you have to go this round-about way is that the type of fun satisfies all of std::predicate<const std::string&>, std::predicate<const char*>, std::predicate<TypeImplicitlyConveribleToString>, there's no unique way to get a type for T. This approach also fails with generic lambdas [](const auto& s) { return s == "toto"; }, so you would need to use make_foo<std::string>(fun).

Artyer
  • 31,034
  • 3
  • 47
  • 75
  • Wahoo, and we don't even need the helper function anymore. Impressive! thanks @Artyer ! – WaterFox Jun 22 '22 at 18:50
  • I'm trying to export that to a more complicated case, would you explain what the "deduction guide" part is about? I can't understand the syntax. – WaterFox Jun 22 '22 at 19:56
  • 1
    @ArnaudBecheler A [deduction guide](https://en.cppreference.com/w/cpp/language/class_template_argument_deduction#User-defined_deduction_guides) is something that that looks like `template ClassName( T1, T2 ) -> ClassName`, it tells the compiler that if it sees `ClassName{ constructor_args... }`, and those constructor args can be deduced (so in the example, if there are two arguments that can deduce `T1` and `T2`) to deduce whats to the right of the arrow. You don't explicitly need CTAD here, you can put it in the `make_foo` function – Artyer Jun 24 '22 at 13:40
  • 1
    `template auto make_foo(P1&& f) { return Foo()})>::type, P1>{ std::forward(f) } }` would be how to do it without CTAD – Artyer Jun 24 '22 at 13:41