Tengo cerca de la siguiente estructura para detectar si el tipo se puede pasar por valor:

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

El problema es: cuando lo instancia para el puntero de función tipo C o std :: function, el compilador dice:

invalid application of 'sizeof' to a function type

(por supuesto).

¿Cómo se puede modificar para que value contenga false?

13
vladon 5 ene. 2017 a las 20:15

4 respuestas

La mejor respuesta

¿Cómo se puede modificar para que ese valor contenga falso?

Cualquier problema puede resolverse con una capa adicional de indirección. Ya tenemos algunos de estos integrados. Básicamente, desea que su verificación de tamaño solo se use cuando T no es una función. Ya hay una metafunción para eso: std::conditional. Podemos usarlo para retrasar la evaluación.

La comprobación de pequeñez, nos separamos en su propia metafunción:

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

Y luego podemos reescribir su condición como:

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

De esta manera, is_small<T> solo se instancia si T no es una función.

9
Barry 5 ene. 2017 a las 21:17

No he podido reproducir el problema exactamente como lo describe, pero si entiendo la pregunta correctamente, puede usar especialización de plantilla para resolver este problema limpiamente. El siguiente ejemplo se compila con Visual Studio 2015 y 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?
};

Aquí hay algunos casos de uso que compilan

// 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;
2
François Andrieux 5 ene. 2017 a las 21:02

Respuesta parcial (como se ha mencionado en los comentarios, no está claro qué no funciona para usted para std :: function, se supone que debe hacerlo)

Puede combinar enable_if_t y is_function para dividir el espacio tipográfico en dos partes, funciones y 'el resto':

#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


}
0
Oleg Bogdanov 5 ene. 2017 a las 19:21

¿Cómo se puede modificar para que ese valor contenga falso?

Sigue una posible implementación para should_be_passed_by_value (en realidad, un ejemplo mínimo y funcional):

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

La idea básica es confiar en una especialización parcial.
Además, en realidad no tiene que definir su propio miembro de datos value. Debido a que está utilizando C ++ 14, should_be_passed_by_value puede heredar directamente de std::false_type y std::true_type.

De forma predeterminada, su tipo T no debe pasarse por valor (should_be_passed_by_value hereda de std::false_type).
Si T no pasa todas las comprobaciones, la especialización se descarta debido a cómo funciona std::enable_if_t. Por lo tanto, la plantilla principal se recoge y eso significa que T no se debe pasar por valor.
Si T pasa todos los controles, std::enable_if_t es void y se prefiere la especialización sobre la plantilla principal. Tenga en cuenta que la especialización se hereda de std::true_type y eso significa que T se debe pasar por valor en este caso.

Como puede ver en el ejemplo, std::function, los tipos de función y todas las demás cosas se tratan de manera fácil y transparente, sin adiciones a su expresión original.

4
skypjack 6 ene. 2017 a las 07:53