SNJ
SNJ

Reputation: 183

c++ type trait to detect if any function argument is reference

I need a type trait to detect if any of the function parameters of a template argument is a reference. This code is working, the trait is "is_any_param_reference" and the static_assert is trigged the signature of foo is changed form void foo( std::string s, int i) to void foo( std::string& s, int i) (first parameter converted to reference).

But id did not work with lambdas... It does not compile if I use:

int main()
{ 
    auto foo2 = [](std::string s, int i){ std::cout << s << " " << i << std::endl; };
    some_function( foo2, s, i  );
}

Any idea of how to generate a type traits that works also for lambda?

Thanks!!

#include <iostream>
#include <string>
#include <type_traits>

using namespace std;

template<typename ... A>
struct any_is_reference : std::false_type {};
template<typename A, typename ... P>
struct any_is_reference<A, P...>: std::conditional_t< std::is_reference<A>::value , std::true_type, any_is_reference<P...> >::type{};

template<typename Sig> struct is_any_param_reference;
template<typename R, typename...Args>
struct is_any_param_reference<R(*)(Args...)>: any_is_reference<Args...>{};

template< typename Sig >
inline constexpr bool is_any_param_reference_v = is_any_param_reference<Sig>::value;

template< typename F , typename ... Args>
void some_function( F f, Args... args)
{
    static_assert(!is_any_param_reference< F >::value, "FUNCTION MUST NOT HAVE PARAMETERS BY REFERENCE");
    
    f(args...);
}

void foo( std::string s, int i)
{
    std::cout << s << " " << i << std::endl;
}

int main()
{
    const std::string s = "Hello";
    int i = 0;
    some_function(foo, s, i);
}

Upvotes: 4

Views: 1283

Answers (2)

Larry
Larry

Reputation: 968

A year and change later but you can more easily accomplish this using my free open source function traits library which is (effectively) a single drop-in header for handling function traits (for C++17 and later). See here for details. It's production-grade and fully documented. I wrote it because I wasn't satisfied with the (very few) other libraries out there that handle function traits including Boost's version (more about this at the above link). The library is for the benefit of everyone however since it's effectively a C++ extension for handling function traits which is missing in the C++ standard itself (for most intents and purposes).

For your situation check out Looping through all function arguments which I've applied to handle your situation in the code below. It demonstrates the ForEachArg function template used to iterate all parameter types in its "F" template arg ("F" can be any function including function pointers, references to functions, functors, etc.). For each parameter encountered in "F", ForEachArg invokes a callback functor that you pass as the only argument to ForEachArg. Your callback functor is actually a template in its own right and on each iteration it receives the next parameter in "F" (its zero-based index in your function and its type). You can therefore simply check the current parameter type on each iteration to see if it's a reference and if so, return false to immediately exit ForEachArg. The latter function therefore stops iterating and returns "false" which is equivalent to a break statement in a regular "for" loop. In your case its false return value indicates that looping of all parameters stopped because you encountered one that's a reference.

Note that the following relies on a lambda template to carry out its work if targeting C++20 or later (lambda templates only became available in C++20), or a normal functor if targeting C++17 (same code as the lambda template but using a normal functor instead). The lambda template is less verbose as seen (so I personally prefer it) but the C++17 code will also work in C++20 and later as well so you can always eliminate the C++20 code if you wish and just (exclusively) rely on the C++17 code (no need to check the C++ version in this case as the code will work in C++17 and later).

To run the code click here:

// Standard C/C++ headers
#include <cstddef>
#include <string>
#include <iostream>

//////////////////////////////////////////////////////
// "FunctionTraits" library. See
// https://github.com/HexadigmSystems/FunctionTraits
//////////////////////////////////////////////////////
#include "TypeTraits.h"

/////////////////////////////////////////////
// Everything in header just above declared
// in this namespace
/////////////////////////////////////////////
using namespace StdExt;

//////////////////////////////////////////////////////
// Following code uses a lambda template which isn't
// available until C++20 (less verbose than the C++17
// code just below so I personally prefer it)
//////////////////////////////////////////////////////
#if CPP20_OR_LATER
    // See https://github.com/HexadigmSystems/FunctionTraits#looping-through-all-function-arguments
    template <TRAITS_FUNCTION_C F>
    constexpr bool is_any_param_reference = !ForEachArg<F>([]<std::size_t I, typename ArgTypeT>() noexcept
                                                          {
                                                               return !std::is_reference_v<ArgTypeT>;
                                                          });
////////////////////////////////////////////////////////
// Lambda templates not supported in C++17 so roll our
// own functor instead (equivalent to lambda template
// above). The following can be used in C++20 or later
// as well if you wish to eliminate the C++20 code
// above and just rely on the following exclusively (in
// C++17 and later)
////////////////////////////////////////////////////////
#elif CPP17
    // Functor identical to lambda template above
    struct HasReferenceArg
    {
        template <std::size_t I, typename ArgTypeT>
        constexpr bool operator()() const noexcept
        {
            return !std::is_reference_v<ArgTypeT>;
        }
    };

    // See https://github.com/HexadigmSystems/FunctionTraits#looping-through-all-function-arguments
    template <typename F>
    constexpr bool is_any_param_reference = !ForEachArg<F>(HasReferenceArg());
#else
    #error "This program is only supported in C++17 or later (an earlier version was detected). Please set the appropriate compiler option to target C++17 or later and try again ("-std=c++17" in GCC, Clang and Intel, or "/std:c++17" in MSFT - later versions also supported of course)"
#endif

template< typename F , typename ... Args>
void some_function( F f, Args... args)
{
    static_assert(!is_any_param_reference<F>, "FUNCTION MUST NOT HAVE PARAMETERS BY REFERENCE");
    
    f(args...);
}

void foo(std::string s, int i)
{
    std::cout << s << " " << i << std::endl;
}

int main()
{
    const std::string s = "Hello";
    int i = 0;

    some_function(foo, s, i);

    auto foo2 = [](std::string s, int i){ std::cout << s << " " << i << std::endl; };
    some_function(foo2, s, i);

    return 0;
}

Upvotes: -1

Igor Tandetnik
Igor Tandetnik

Reputation: 52591

template<typename T> struct is_any_param_reference :
    is_any_param_reference<decltype(&T::operator())>{};

template<typename R, typename...Args>
struct is_any_param_reference<R(*)(Args...)>: any_is_reference<Args...>{};

template<typename T, typename R, typename...Args>
struct is_any_param_reference<R(T::*)(Args...)>: any_is_reference<Args...>{};
template<typename T, typename R, typename...Args>
struct is_any_param_reference<R(T::*)(Args...) const>: any_is_reference<Args...>{};

Demo.

The primary template assumes that its argument is a class providing operator() (e.g. a lambda). Then there are specializations for a plain function pointer as well as a pointer-to-member-function. The primary template effectively delegates to this last specialization.

Upvotes: 6

Related Questions