2
\$\begingroup\$

Given any callable, deduces the first parameter in the parameter list of the function. Be ware, not for light hearted.

template <typename ... Args>
struct pick_first;

template <typename T, typename ... Args>
struct pick_first<T, Args ...>
{
    using type = T;
};

template <typename ... Args>
using pick_first_t = typename pick_first<Args...>::type;

template <typename Callable>
struct deduce_first_parameter
{
private:
    template <typename Func, template <typename ...> typename Operation>
    struct extract_function;

    template <typename R, typename ... Args, template <typename ...> typename Operation>
    struct extract_function<R(Args...), Operation>
    {
        using type = typename Operation<Args...>::type;
    };

    template <typename C, typename R, typename ... Args, template <typename ...> typename Operation>
    struct extract_function<R (C::*)(Args...), Operation>
    {
        using type = typename Operation<Args...>::type;
    };

    template <typename Func, template <typename ...> typename Operation>
    using extract_function_t = typename extract_function<Func, Operation>::type;

public:
    using type = extract_function_t<decltype(&Callable::operator()), pick_first>;
};

template <typename R, typename ... InputArgs>
struct deduce_first_parameter<R(InputArgs...)>
{
    using type = pick_first_t<InputArgs...>;
};

Some tests:

#include <vector>
#include <map>

struct dummy
{
    void operator()(std::vector<int>&) {}
    using correct_answer = std::vector<int>&;
};

struct not_so_dummy
{
    using correct_answer = const std::vector<std::map<int, char>>&;
    void operator()(const std::vector<std::map<int, char>>&) {}

};

void f(char, int);

int main()
{
    static_assert(std::is_same<deduce_first_parameter<decltype(f)>::type, char>::value);
    static_assert(std::is_same<deduce_first_parameter<dummy>::type, dummy::correct_answer >::value);
    static_assert(std::is_same<deduce_first_parameter<not_so_dummy>::type, not_so_dummy::correct_answer>::value);
}

After about two weeks of intense Java, I started missing C++. Especially templates.

Motivation:

I'm writing generic sliding window, which is complete, but I don't want to show it yet (I figured that it is possible to do stunning things with it). To supplement it, I need preprocessing iterator, which applies a user supplied functor before assigning to underlying iterator. To make the preprocessing iterator easy to use I need to deduce the first parameter's type to put into std::iterator<> (I know its deprecated, but some compilers still use it to detect iterators), because binding users to what underlying iterator can accept is awkward.

How to compile:

I'm afraid not that many compilers can handle this. I've tested it on clang++-3.9. There might be some warnings, they should be due to using C++17 feature to make template template parameters declared by typename possible (good luck on understanding this sentence).

I'm interested in any critique, but especially about reducing instantiation depth and count. Also fitting statements in one readable line would be great.

\$\endgroup\$
  • 1
    \$\begingroup\$ P.S. About the title, I wanted it to be a clickbait. Not sure if I achieved my goal, so if you think it is not appropriate feel free to edit to something more useful (though the code is template metaprogramming madness). Please don't edit if you think it is interesting and attracting. \$\endgroup\$ – Incomputable Feb 17 '17 at 22:49
4
\$\begingroup\$

You can figure out the first parameter of a non-overloaded function pointer pretty easily; I'm not sure but your code looks more complicated than it ought to be.

Unfortunately, your code completely falls down in the presence of overloads and/or templates. Here's the error message:

prog.cc:36:46: error: reference to overloaded function could not be resolved; did you mean to call it?
    using type = extract_function_t<decltype(&Callable::operator()), pick_first>;
                                             ^~~~~~~~~~~~~~~~~~~~~

and here are some types that trigger it:

struct oops1 {
    void operator()(int);
    void operator()(double);
};

struct oops2 {
    void operator()(int);
    void operator()(int) const;
};

void oops3(int);
void oops3(double);

struct oops4
{
    using correct_answer = const std::vector<std::map<int, char>>&;
    template<class T, std::enable_if_t<std::is_same_v<T, const std::vector<std::map<int, char>>>, int> = 0>
        void operator()(T&) {}
};

And here's another error message that I get:

prog.cc:11:1: error: implicit instantiation of undefined template 'pick_first<>'
using pick_first_t = typename pick_first<Args...>::type;
^

with this input:

void oops5(void);

If I were tackling this problem, I would first ask myself what "the type of the first parameter" even means in C++, given that we have function overloading and templates. But then I might try to detect the appropriate type by messing around with

struct ConvertibleToAnything {
    template<class T> operator T() const;
};

and then inspecting f(ConvertibleToAnything{}) and trying to figure out which operator T was getting instantiated.

In the cases where overloading and/or templates rendered the question meaningless, I would try to at least give a nice static_assert error message, if not actually produce a tag type so that is_same_v<deduce_first_parameter_t<oops1>, signature_is_ambiguous_t>.

\$\endgroup\$
  • \$\begingroup\$ Yeah, it is not meant for templated functions and overloaded functions. When I started writing it I wanted to get lambda support as fast as possible, because it is the most natural way to use it with preprocessing iterator. Thanks for finding some counter examples. I guess I'll try to deduce it only when it is a function pointer, and leave some assert message for anything else. \$\endgroup\$ – Incomputable Feb 18 '17 at 13:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.