Search code examples
c++c++14template-meta-programmingtype-deduction

C++ template metaprogramming: how to deduce type in expression pattern


I am want a static check of the parameter type of lambdas. I've written this code below and it seems to produce the correct result.

struct B { };
auto lamBc = [](B const& b) { std::cout << "lambda B const" << std::endl; };

template<typename ClosureType, typename R, typename Arg>
constexpr auto ArgType(R (ClosureType::*)(Arg) const)->Arg;

template <typename T>
using ArgType_t = decltype(ArgType(&T::operator()));

// ArgType_t<lamBc> is "reference to B const"

However, I noticed that, for example, the standard library uses class template specialization to extract the referred-to type from a reference type in std::remove_reference. So I tried that approach and it also seems to produce the correct result.

template<typename L>
struct ArgType2;

template<typename ClosureType, typename R, typename Arg>
struct ArgType2<R (ClosureType::*)(Arg) const>
{
    typedef Arg type;
};

template <typename T>
using ArgType2_t = typename ArgType2<decltype(&T::operator())>::type;

// ArgType2_t<lamBc> is also "reference to B const"

My questions are: Which is the standard way to extract types from a pattern expression? What are the trade-offs in either approach?


Solution

  • Both your approaches are valid, interchangeable and lead to the same result (deduce the type of the parameter lambda accepts).

    For type traits it is required by the standard (see 23.15.1 Requirements) that:

    1. A UnaryTypeTrait describes a property of a type. It shall be a class template that takes one template type argument and, optionally, additional arguments that help define the property being described. ...

    2. A BinaryTypeTrait describes a relationship between two types. It shall be a class template that takes two template type arguments and, optionally, additional arguments that help define the relationship being described. ...

    3. A TransformationTrait modifies a property of a type. It shall be a class template that takes one template type argument and, optionally, additional arguments that help define the modification. ...

    I suppose that this requirement appeared mostly for historical reasons as decltype functionality was introduced after type traits had been proposed (and these type traits were based on type traits from boost which had been created even earlier, see, for example, this).

    Also, note, that class templates are more flexible for general purpose type traits than the logic based on functions declarations and decltype.

    The main point is that with C++11 and later in your particular case you are free to use the way which is the most convenient and reflects the programming logic better.