vdavid
vdavid

Reputation: 2584

Type of member functions arguments

Using C++11 I am trying to get the type of function arguments from a function name, granted the function signature is unambiguous. Ultimately, I am trying to check that an argument is exactly the type I want before I call a function.

So far I can do it with non-member functions thanks to this solution: Unpacking arguments of a functional parameter to a C++ template class

I have slightly edited it to get the following class:

template<typename T>
struct FunctionSignatureParser; // unimplemented primary template
template<typename Result, typename...Args>
struct FunctionSignatureParser<Result(Args...)>
{
    using return_type = Result;
    using args_tuple = std::tuple<Args...>;
    template <size_t i> struct arg
    {
        typedef typename std::tuple_element<i, args_tuple>::type type;
    };
};

For instance I can check the type of a function at compile-time:

short square(char x) { // 8-bit integer as input, 16-bit integer as output
    return short(x)*short(x);
}
int main() {
        char answer = 42;
        static_assert(std::is_same<char, FunctionSignatureParser<decltype(square)>::arg<0>::type>::value, "Function 'square' does not use an argument of type 'char'");
        static_assert(std::is_same<short, FunctionSignatureParser<decltype(square)>::return_type>::value, "Function 'square' does not return a value of type 'short'");
        short sqrAnswer = square(answer);
        std::cout << "The square of " << +answer << " is " << +sqrAnswer << std::endl;
        return 0;
}

>> Online code with gcc

But when I want to check the type of member functions, some compilers are not happy with it:

struct Temperature
{
    double degrees;
    Temperature add(double value);
};
int main() {
    Temperature t{16};
    double increment{8};
    static_assert(std::is_same<double, FunctionSignatureParser<decltype(t.add)>::arg<0>::type>::value, "Method 'Temperature::add' does not use an argument of type 'double'");
    std::cout << t.degrees << u8" \u00b0C + " << increment << " == " << t.add(increment).degrees << u8" \u00b0C" << std::endl;
    return 0;
}

Here is what gcc 6.3 has to say:

error: invalid use of non-static member function ‘Temperature Temperature::add(double)’

>> Online code with gcc

Here is what clang 4.0 has to say:

error: reference to non-static member function must be called

>> Online code with clang

I have tried those options, to no avail:

decltype(Temperature::add)
decltype(std::declval<Temperature>().add)

What’s the problem with non-static member functions inside a decltype? Since expressions inside a decltype are not evaluated, the static qualifier should not matter, right?

For the record, MSVC12 succeeds in this situation. I cannot tell if the Microsoft compiler is right or wrong, though. (please let’s not make this thread a compiler war)

Also, if you have a solution to argument checking that does not involve the initial approach, I am open for it as well.

Upvotes: 3

Views: 588

Answers (2)

edrezen
edrezen

Reputation: 1855

Following dvk's answer, here is a little improvement that handles the case where the member function is const. A base class can be defined in order to avoid code duplication.

#include <tuple>
#include <cstddef>

////////////////////////////////////////////////////////////////////////////////

template<typename Result, typename ...Args>  struct FunctionSignatureParserBase
{
    using return_type = Result;
    using args_tuple  = std::tuple<Args...>;
    template <size_t i> struct arg
    {
        typedef typename std::tuple_element<i, args_tuple>::type type;
    };

    template <size_t i>  using arg_t = typename arg<i>::type;
};

template<typename T>  struct FunctionSignatureParser;

template<typename ClassType, typename Result, typename...Args>
struct FunctionSignatureParser<Result(ClassType::*)(Args...)>
  : FunctionSignatureParserBase<Result,Args...>
{
};

template<typename ClassType, typename Result, typename...Args>
struct FunctionSignatureParser<Result(ClassType::*)(Args...) const>
  : FunctionSignatureParserBase<Result,Args...>
{
};

////////////////////////////////////////////////////////////////////////////////
struct foo { void operator() (int n)       {}  };
struct bar { void operator() (int n) const {}  };

using foo_t = typename FunctionSignatureParser<decltype(&foo::operator())>::arg_t<0>;
using bar_t = typename FunctionSignatureParser<decltype(&bar::operator())>::arg_t<0>;

static_assert(std::is_same<int, foo_t>::value == true);
static_assert(std::is_same<int, bar_t>::value == true);

int main() {}

Upvotes: 1

dvk
dvk

Reputation: 1480

You need another template specialization for member functions, something like this:

template<typename ClassType, typename Result, typename...Args>
struct FunctionSignatureParser<Result(ClassType::*)(Args...)>
{
    using return_type = Result;
    using args_tuple = std::tuple<Args...>;
    template <size_t i> struct arg
    {
        typedef typename std::tuple_element<i, args_tuple>::type type;
    };
};

And it can be used like this:

    static_assert(std::is_same<double, FunctionSignatureParser<decltype(&Temperature::add)>::arg<0>::type>::value, "Method 'Temperature::add' does not use an argument of type 'double'");

This works with gcc 7.2, haven't tested other compilers

Upvotes: 6

Related Questions