Question: Don't use sizeof for T if T is a function

Question

Don't use sizeof for T if T is a function

Answers 4
Added at 2017-01-05 18:01
Tags
Question

I have near following struct for detecting if type can be passed by value:

template <class T>
struct should_be_passed_by_value {
    static constexpr bool value = 
        std::is_scalar<T>::value || 
        std::is_array<T>::value || 
        std::is_reference<T>::value || 
        (sizeof(T) <= sizeof(void*));
};

Problem is: when I instantiate it for C-like function pointer or std::function, the compiler says:

invalid application of 'sizeof' to a function type

(of course).

How can it be modified so that value will contain false?

Answers
nr: #1 dodano: 2017-01-05 20:01

Partial answer (As its been mentioned in comments it's not clear what does not work for you for std::function, it is supposed to)

You could combineenable_if_t and is_function to divide type space into two parts, functions and 'the rest' :

#include <type_traits>
#include <functional>
#include <iostream>

template <class T, class Enable = void>
struct should_be_passed_by_value; // primary case that we will never hit

template <class T>
struct should_be_passed_by_value<T, typename std::enable_if_t<std::is_function<T>::value>>
{
  static constexpr bool value = false; // case 0
};

template <class T>
struct should_be_passed_by_value<T, typename std::enable_if_t<!std::is_function<T>::value>>
{
  static constexpr bool value =
      std::is_scalar<T>::value || // case 1
      std::is_array<T>::value || // case 2
      std::is_reference<T>::value || // case 3
      (sizeof(T) <= sizeof(void *)); /// case 4
};

void testF(){};

int main()
{
  std::function<void()> f;
  std::cout << "should_be_passed_by_value1 " << should_be_passed_by_value<decltype(testF)>::value << std::endl; // result 0, case 0

  std::cout << "should_be_passed_by_value1 " << should_be_passed_by_value<decltype(5)>::value << std::endl; // res 1, case 1

  std::cout << "should_be_passed_by_value1 " << should_be_passed_by_value<char[42]>::value << std::endl; // res 1, case 2

  std::cout << "should_be_passed_by_value1 " << should_be_passed_by_value<int&>::value << std::endl; // res 1 , case 3

  struct Small {char _{2};};
  std::cout << "should_be_passed_by_value1 " << should_be_passed_by_value<Small>::value << std::endl; // res 1, case 4

  struct Big {char _[16];};
  std::cout << "should_be_passed_by_value1 " << should_be_passed_by_value<Big>::value << std::endl; // res 0, case 4


}
nr: #2 dodano: 2017-01-05 20:01

I haven't been able to reproduce the problem exactly as you describe it, but if I understand the question correctly you can use template specialization to cleanly solve this problem. The following example compiles with Visual Studio 2015 and gcc 4.9.

#include <type_traits>

// Non-function types
template <class T>
struct should_be_passed_by_value 
{
    static constexpr bool value = 
        std::is_scalar<T>::value || 
        std::is_array<T>::value || 
        std::is_reference<T>::value || 
        (sizeof(T) <= sizeof(void*));
};

// Function type
template <class Return, class ... Args>
struct should_be_passed_by_value<Return(Args...)> 
{
    static constexpr bool value = false; // What value for functions?
};

Here are some use case that compile

// All of these use cases compile
#include <array>
const auto u = should_be_passed_by_value<std::array<int, 10>>::value;
const auto v = should_be_passed_by_value<int*()>::value;
const auto w = should_be_passed_by_value<int()>::value;
const auto x = should_be_passed_by_value<int(int)>::value;
const auto y = should_be_passed_by_value<int*>::value;
const auto z = should_be_passed_by_value<int>::value;
nr: #3 dodano: 2017-01-05 21:01

How can it be modified so that value will contain false?

Any problem can be solved with an extra layer of indirection. We have some of these built in already. Basically, you want your smallness check to only be used when T is not a function. There's a metafunction for that already: std::conditional. We can use it to delay evaluation.

The smallness check, we separate out into its own metafunction:

template <class T>
struct is_small
    : std::integral_constant<bool, (sizeof(T) <= sizeof(void*))>
{ };

And then we can rewrite your condition as:

template <class T>
struct should_be_passed_by_value {
    static constexpr bool value = 
        std::is_scalar<T>::value || 
        std::is_array<T>::value || 
        std::is_reference<T>::value || 
        std::conditional_t<
            std::is_function<T>::value,
            std::false_type,
            is_small<T>>::value;
};

This way, is_small<T> is only instantiated if T is not a function.

nr: #4 dodano: 2017-01-06 00:01

How can it be modified so that value will contain false?

It follows a possible implementation for should_be_passed_by_value (actually a minimal, working example):

#include<type_traits>
#include<functional>

template <class T, typename = void>
struct should_be_passed_by_value: std::false_type {};

template <class T>
struct should_be_passed_by_value
<T, std::enable_if_t<
    (std::is_scalar<T>::value ||
    std::is_array<T>::value ||
    std::is_reference<T>::value || 
    (sizeof(T) <= sizeof(void*)))
>>: std::true_type {};

void f() {}

int main() {
    static_assert(should_be_passed_by_value<int>::value, "!");
    static_assert(should_be_passed_by_value<char>::value, "!");
    static_assert(not should_be_passed_by_value<std::function<void(void)>>::value, "!");
    static_assert(not should_be_passed_by_value<void(void)>::value, "!");
    static_assert(should_be_passed_by_value<void(*)(void)>::value, "!");
}

The basic idea is to rely on a partial specialization.
Moreover, you don't really have to define your own value data member. Because you are using C++14, should_be_passed_by_value can inherit directly from std::false_type and std::true_type.

By default, your type T should not be passed by value (should_be_passed_by_value inherits from std::false_type).
If T doesn't pass all the checks, the specialization is discarded because of how std::enable_if_t works. Therefore the primary template is picked up and that means that T shouldn't be passed by value.
If T passes all the checks, std::enable_if_t is void and the specialization is preferred over the primary template. Note that the specialization inherits from std::true_type and that means that T should be passed by value in this case.

As you can see from the example, std::function, function types and all the other things are treated easily and transparently, with no additions to your original expression.

Source Show
◀ Wstecz