简体   繁体   中英

Resolving to different types based on the argument types of a c++ template function

I'm doing some metaprogramming and I have ran into the following problem:

I have a class that takes one template parameter T , T can be assumed to be a function with an arbitary signature. The class a member variable V , that should have the type std::tuple<> if T takes no arguments or the first argument is not a std::tuple. If the first argument is an std::tuple , V should instead have the same type as first argument.

Example:

 void f() // Should resolve to std::tuple<>
 void f(int) // Should resolve to std::tuple<>
 void f(std::tuple<int, float>) // Should resolve to std::tuple<int, float>
 void f(std::tuple<float>, int) // Should resolve to std::tuple<float>

I have been trying something similar to this, but with no success. As it fails when indexing the first arguement on the argument free function, without selecting any of the other alternatives in spite of those being available. I'm using MSVC 2019 16.8.4

#include <functional>
#include <concepts>
namespace detail
{
    template<typename... ArgTs>
    struct HasArgs : public std::conditional<(sizeof... (ArgTs) > 0), std::true_type, std::false_type>::type {};
}

//!
//! Provides argument function information
//! Based on: https://stackoverflow.com/a/9065203
//! 
template<typename T>
class FunctionTraits;

template<typename R, typename... Args>
class FunctionTraits<R(Args...)>
{
public:
    static const size_t arg_count = sizeof...(Args);
    using HasArguments = detail::HasArgs<Args...>;

    using ReturnType = R;
    using ArgTypes = std::tuple<Args...>;

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

namespace detail
{
template <typename T>
struct is_tuple : std::false_type {};

template <typename... Args>
struct is_tuple<std::tuple<Args...>>: std::true_type {};
}

template <typename T>
concept is_tuple = requires() { detail::is_tuple<T>::value; };


class TestMemberFunctions
{
public:
    static int test_f1(std::tuple<int, float>, int)
    {
        return 0;
    }

    static int test_f2(int)
    {
        return 0;
    }
    static int test_f3()
    {
        return 0;
    }

};

template <typename CreateT> requires (!FunctionTraits<CreateT>::HasArguments::value)
std::tuple<> TypeDeductionDummyFunction();


template <typename CreateT> requires FunctionTraits<CreateT>::HasArguments::value
auto TypeDeductionDummyFunction() -> std::conditional<is_tuple<typename FunctionTraits<CreateT>::template arg<0>::type>,
                                                                                                typename FunctionTraits<CreateT>::template arg<0>::type,
                                                                                                std::tuple<>>;

template <typename T>
class SampleClass
{
    decltype(TypeDeductionDummyFunction<T>()) m_member;
};

SampleClass<decltype(TestMemberFunctions::test_f1)> c1; 
SampleClass<decltype(TestMemberFunctions::test_f2)> c2; 
SampleClass<decltype(TestMemberFunctions::test_f3)> c3; 


Something along these lines, perhaps:

template <typename T> struct ExtractFirstTuple;

template <typename R>
struct ExtractFirstTuple<R()> {
    using type = std::tuple<>;
};

template <typename R, typename... Ts, typename... Args>
struct ExtractFirstTuple<R(std::tuple<Ts...>, Args...)> {
    using type = std::tuple<Ts...>;
};

template <typename R, typename First, typename... Args>
struct ExtractFirstTuple<R(First, Args...)> {
    using type = std::tuple<>;
};

Demo

An attempt to build what you want from more primitive operations.

template<typename T, std::size_t N>
struct FunctionArgument {
  static constexpr bool exists = false;
};

template<typename R, typename A0, typename... Args>
struct FunctionArgument<R(A0, Args...), 0>{
  using type=A0;
  static constexpr bool exists = true;
};

template<typename R, typename A0, typename... Args, std::size_t N>
struct FunctionArgument<R(A0, Args...), N>:
  FunctionArgument<R(Args...), N-1>
{};

template<class Sig, std::size_t N>
using FuncArg_type = typename FunctionArgument<Sig, N>::type;
template<class Sig, std::size_t N>
constexpr bool FuncArg_exists = FunctionArgument<Sig, N>::exists;

template<class Sig, class Otherwise>
using FirstArgIfExists = 
  typename std::conditional_t<
    FuncArg_exists<Sig,0>,
    FunctionArgument<Sig, 0>,
    std::type_identity<Otherwise>
  >::type;
template<class T, class Otherwise>
struct TypeIfTuple {
  using type=Otherwise;
};
template<class...Ts, class Otherwise>
struct TypeIfTuple<std::tuple<Ts...>, Otherwise> {
  using type=std::tuple<Ts...>;
};
template<class T, class Otherwise>
using TypeIfTuple_t = typename TypeIfTuple<T,Otherwise>::type;

template<class Sig>
using TheTypeYouWant = TypeIfTuple_t<
  FirstArgIfExists<Sig, std::tuple<>>,
  std::tuple<>
>;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM