Подтвердить что ты не робот

Не используйте sizeof для T, если T - функция

У меня есть следующая структура для определения того, может ли тип передаваться по значению:

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*));
};

Проблема заключается в следующем: когда я создаю экземпляр для указателя на функцию C или std:: function, компилятор говорит:

invalid application of 'sizeof' to a function type

(конечно).

Как его можно изменить так, чтобы value содержал false?

4b9b3361

Ответ 1

Как его можно изменить так, чтобы значение содержало false?

Любая проблема может быть решена с помощью дополнительного слоя косвенности. У нас уже есть некоторые из них. В принципе, вы хотите, чтобы ваша проверка на малость использовалась только тогда, когда T не является функцией. Для этого уже есть метафору: std::conditional. Мы можем использовать его для задержки оценки.

Проверка малости, мы выделяем ее собственную метафору:

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

И тогда мы можем переписать ваше условие как:

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;
};

Таким образом, is_small<T> создается только тогда, когда T не является функцией.

Ответ 2

Как его можно изменить так, чтобы значение содержало false?

Это следует за возможной реализацией для should_be_passed_by_value (фактически минимального рабочего примера):

#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, "!");
}

Основная идея - полагаться на частичную специализацию.
Более того, вам не обязательно определять свой собственный элемент данных value. Поскольку вы используете С++ 14, should_be_passed_by_value может наследовать непосредственно из std::false_type и std::true_type.

По умолчанию ваш тип T не должен передаваться по значению (should_be_passed_by_value наследуется от std::false_type).
Если T не проходит все проверки, специализация отбрасывается из-за того, как работает std::enable_if_t. Поэтому первичный шаблон подбирается, а это означает, что T не следует передавать по значению.
Если T проходит все проверки, std::enable_if_t - void, и специализация предпочтительнее основного шаблона. Обратите внимание, что специализация наследуется от std::true_type, а это означает, что T должен быть передан по значению в этом случае.

Как видно из примера, std::function, типы функций и все другие вещи обрабатываются легко и прозрачно, без добавлений к исходному выражению.

Ответ 3

Я не смог воспроизвести проблему точно так, как вы ее описали, но если я правильно понял вопрос, вы можете использовать специализация шаблона чтобы решить эту проблему. Следующий пример компилируется с Visual Studio 2015 и 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?
};

Вот пример использования, который компилирует

// 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;

Ответ 4

Частичный ответ (Как уже упоминалось в комментариях, не ясно, что не работает для вас для функции std::, предполагается, что)

Вы можете комбинировать enable_if_t и is_function для разделения пространства типов на две части, функции и "остальные":

#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


}