Dadas las siguientes declaraciones:

struct MyClass { };
typedef int MyClass::*Mp;

Tanto en el compilador gcc 6.2 como en el compilador Clang que he probado, result_of<Mp(const MyClass)>::type produce int&&.

Resumen de mi pregunta: ¿por qué int&& y no const int&& o simplemente int?

Más antecedentes: el estándar dice que result_of se define de esta manera:

el miembro typedef type debe nombrar el tipo decltype(INVOKE(declval<Fn>(), declval<ArgTypes>()...));

El estándar también define INVOKE para objetos de puntero a miembro de esta manera:

- t1. * F cuando N == 1 yf es un puntero a un miembro de datos de una clase T y is_base_of_v<T, decay_t<decltype(t1)>> es verdadero;

Tenga en cuenta que el decay_t es solo para probar si se aplica esta viñeta. Por lo que puedo decir, la aplicación de los dos puntos anteriores debería producir:

decltype(declval<const MyClass>().*declval<Mp>())

Lo que produce const int&&. Entonces, ¿me falta algo o las bibliotecas del compilador están mal?

Editar, 30 de agosto de 2016:

Gracias por las respuestas. Varias personas han sugerido formas alternativas de obtener el resultado correcto sin usar result_of. Debo aclarar que la razón por la que estoy obsesionado con la definición correcta de result_of es que en realidad estoy implementando la implementación razonable más cercana de result_of que funciona con un compilador anterior a C ++ 11. Entonces, aunque estoy de acuerdo en que puedo usar decltype o result_of<Mp(const MyClass&&)>::type en C ++ 11, no hacen lo que necesito para C ++ 03. Varias personas han dado la respuesta correcta, que es que los argumentos const rvalue para funciones no son parte del tipo de función. Esto me aclara las cosas e implementaré mi versión anterior a C ++ 11 result_of de manera que también descarte esos calificadores.

20
Pablo Halpern 28 ago. 2016 a las 03:07

2 respuestas

La mejor respuesta

const se quita de los parámetros de la función. Puede verificar esto usando is_same.

void(int) == void(const int)
Mp(MyClass) == Mp(const MyClass)
result_of<Mp(MyClass)> == result_of<Mp(const MyClass)>

Creo que esto se explica por [8.3.5.5]:

Después de producir la lista de tipos de parámetros, cualquier nivel superior Los calificadores cv que modifican un tipo de parámetro se eliminan al formar el tipo de función. La lista resultante de tipos de parámetros transformados y la presencia o ausencia de elipsis o un paquete de parámetros de función es la lista de tipos de parámetros de la función. [Nota: esta transformación no afecta los tipos de parámetros. Por ejemplo, int(*)(const int p, decltype(p)*) y int(*)(int, const int*) son tipos idénticos. - nota final]

Puede solucionarlo definiendo su propio result_of que no utilice (mal) los tipos de función:

template <typename F, typename... ArgTypes>
struct my_result_of
{
    using type = decltype(std::invoke(std::declval<F>(), std::declval<ArgTypes>()...));
};

Esta definición es realmente la que debería haber utilizado el estándar.

8
Pubby 28 ago. 2016 a las 04:22

En result_of_t<Mp(const MyClass)> parece que estás intentando preguntar cuál es el tipo de resultado de invocar Mp con un const rvalue de tipo MyClass. Una mejor manera de preguntar eso con result_of sería result_of_t<Mp(const MyClass&&)>, pero generalmente es más fácil usar decltype y olvidar que result_of alguna vez existió. Si realmente pretendiera preguntar el resultado con un valor const, entonces ese sería result_of_t<Mp(const MyClass&)>.

Es cierto que el nivel superior const en parámetros de función no tiene significado en una declaración de función. Cuando se usa result_of, por lo tanto, tiene más sentido proporcionar tipos de argumentos como referencias a posibles - const tipos. Esto también hace que la categoría de valor sea explícita, sin pérdida de expresividad. Podemos usar el truco print_type para ver qué sucede cuando hacemos esto:

template <typename...> struct print_type; // forward declaration

print_type<std::result_of_t<Mp(const MyClass)>,
           std::result_of_t<Mp(const MyClass&)>,
           std::result_of_t<Mp(const MyClass&&)>,
           std::result_of_t<Mp(MyClass)>,
           std::result_of_t<Mp(MyClass&)>,
           std::result_of_t<Mp(MyClass&&)>>{};

Esto imprime:

error: invalid use of incomplete type 'struct print_type<int&&, const int&, const int&&, int&&, int&, int&&>'

Entonces podemos deducir:

std::result_of_t<Mp(const MyClass)>   == int&&
std::result_of_t<Mp(const MyClass&)>  == const int&
std::result_of_t<Mp(const MyClass&&)> == const int&&
std::result_of_t<Mp(MyClass)>         == int&&
std::result_of_t<Mp(MyClass&)>        == int&
std::result_of_t<Mp(MyClass&&)>       == int&&

Podemos ver que result_of_t<Mp(const MyClass)>, result_of_t<Mp(MyClass)> y result_of_t<Mp(MyClass&&)> significan lo mismo. Me sorprendería que no lo hicieran.

Tenga en cuenta que cuando usa declval también está proporcionando tipos de argumentos como referencias, ya que declval se declara para devolver una referencia. Además, todos los parámetros de std::invoke son referencias.

5
Oktalist 28 ago. 2016 a las 12:29