0

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; 


Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
OnePie
  • 425
  • 3
  • 10
  • 1
    Hint: what is `HasArguments`? – Barry Feb 07 '21 at 19:03
  • There was a problem I left in the code by accident after doing some experimentation that Barry noticed, I missed it when cleaning it up for this website. The problem is removed, none the less it was not the cause as it does not compile regardless. – OnePie Feb 07 '21 at 19:37
  • `std::conditional` doesn't short-circuit - both branches are instantiated, both need to be well-formed. – Igor Tandetnik Feb 07 '21 at 20:39

2 Answers2

2

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

Igor Tandetnik
  • 50,461
  • 4
  • 56
  • 85
0

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<>
>;
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524