2

I am trying to implement a recursive version std::invoke_result_t with C++20 concept so that the type of nested invoke result can be retrieved.

The use case of recursive_invoke_result_t

auto f1 = [](int x) { return static_cast<double>(x); };
std::cout << typeid(recursive_invoke_result_t<decltype(f1), int>).name() << std::endl;                  //  expected output "double"

std::cout << typeid(recursive_invoke_result_t<decltype(f1), std::vector<int>>).name() << std::endl;     //  expected output is something like "std::vector<double>"

std::cout << typeid(recursive_invoke_result_t<decltype(f1), std::vector<std::vector<int>>>).name() << std::endl;     //  expected output is something like "std::vector<std::vector<double>>"

auto f2 = [](int x) { return std::to_string(x); };
std::cout << typeid(recursive_invoke_result_t<decltype(f2), std::vector<int>>).name() << std::endl;         //  expected output is something like "std::vector<string>"

auto f3 = [](std::string x) { return std::atoi(x.c_str()); };
std::cout << typeid(recursive_invoke_result_t<decltype(f3), std::vector<std::string>>).name() << std::endl;         //  expected output is something like "std::vector<int>"

The experimental implementation

The experimental implementation is as below.

//  recursive_invoke_result_t implementation
template<typename F, typename T>
requires (std::invocable<F, T>)
struct recursive_invoke_result_t_detail
{
    using type = std::invoke_result_t<F, T>;
};

template <typename F, template <typename...> typename Container, typename... Ts>
requires (std::ranges::input_range<std::ranges::range_value_t<Container<Ts...>>>) && (!std::invocable<F, Container<Ts...>>)
struct recursive_invoke_result_t_detail<F, Container<Ts...>>
{
    using type = Container<typename recursive_invoke_result_t_detail<F, std::iter_value_t<Container<Ts...>>>::type>;
};

template<typename F, typename T>
using recursive_invoke_result_t = typename recursive_invoke_result_t_detail<F, T>::type;

In the above experimental implementation, the test case recursive_invoke_result_t<decltype(f1), int> seems to be working fine. When it comes to the second test case recursive_invoke_result_t<decltype(f1), std::vector<int>>, the compile errors 'recursive_invoke_result_t_detail': the associated constraints are not satisfied, 'recursive_invoke_result_t' : Failed to specialize alias template and unable to recover from previous error(s); stopping compilation occurred. Is there any way to solve this? Please give me some hints or examples.

JimmyHu
  • 403
  • 1
  • 8
  • 20

1 Answers1

3

This

template<typename F, typename T> requires std::invocable<F, T>
struct recursive_invoke_result_t_detail
{
    using type = std::invoke_result_t<F, T>;
};

is the primary template for recursive_invoke_result_t_detail. The requires std::invocable<F, T> then means that recursive_invoke_result_t_detail<F, T> is only valid if std::invocable<F, T>. Period. No specialization can relax this requirement; it is a property of the template itself. The requires constraint on the specialization is just an added constraint: the template can only be instantiated with <F, T> if std::invocable<F, T>, and that partial specialization only applies if, further, T = Container<Ts...> for some Ts... and the other constraint you gave is satisfied (which is actually impossible!)

Leave the primary template unconstrained (and also name and alias it according to convention)

template<typename, typename>
struct recursive_invoke_result { };
template<typename F, typename T>
using recursive_invoke_result_t = recursive_invoke_result<F, T>::type;

Give a partial specialization for the base case

template<typename T, std::invocable<T> F>
struct recursive_invoke_result<F, T> { using type = std::invoke_result_t<F, T>; };

and then your other partial specialization

template<typename F, template<typename...> typename Container, typename... Ts>
requires (
    !std::invocable<F, Container<Ts...>> && // don't conflict with base case
    std::ranges::input_range<Container<Ts...>> && // no idea why you asked for the contained type to be range, fixed it
    requires { typename recursive_invoke_result_t<F, std::ranges::range_value_t<Container<Ts...>>>; }) // SFINAE-compatibility
struct recursive_invoke_result<F, Container<Ts...>> {
    using type = Container<recursive_invoke_result_t<F, std::ranges::range_value_t<Container<Ts...>>>>; // not iter_value_t, the container isn't an iterator!
};

Yum.

// typeid(...).name() doesn't produce anything readable for me on Godbolt
template<typename T>
std::ostream &print_type(std::ostream &out) {
    // stealing https://stackoverflow.com/a/20170989
    // works only for GCC
    std::string_view name = __PRETTY_FUNCTION__;
    name.remove_prefix(50);
    name.remove_suffix(42);
    return out << name;
};
template<typename T>
constexpr void assert_no_type() { }
template<typename T> requires requires { typename T::type; }
void assert_no_type() = delete;

int main() {
    using F = decltype([](int x) -> double { return x; });
    std::cout << "double(int), int: " << print_type<recursive_invoke_result_t<F, int>> << "\n";
    std::cout << "double(int), std::vector<int>: " << print_type<recursive_invoke_result_t<F, std::vector<int>>> << "\n";
    assert_no_type<recursive_invoke_result<F, std::vector<std::ostream>>>();
    std::cout << "double(int), std::vector<std::vector<int>>: " << print_type<recursive_invoke_result_t<F, std::vector<std::vector<int>>>> << "\n";
    std::cout << "double(int), std::vector<std::set<int>>: " << print_type<recursive_invoke_result_t<F, std::vector<std::set<int>>>> << "\n";
    using G = decltype([](std::vector<int> const &v) -> std::vector<double> { return {}; });
    assert_no_type<recursive_invoke_result<G, std::set<std::set<int>>>>();
    std::cout << "std::vector<double>(std::vector<int>): " << print_type<recursive_invoke_result_t<G, std::set<std::vector<int>>>> << "\n";
}

Godbolt

HTNW
  • 27,182
  • 1
  • 32
  • 60
  • 2
    You might even use `static_assert(std::is_same_v);` instead of printing. for missing `type`, it is also doable, but requires extra helper. – Jarod42 Dec 30 '20 at 09:01