c++ - una - redefinición de metodos en clases derivadas
¿Qué significa heredar de lambda? (3)
Bueno, ese código se compilará, pero el problema es que no podrá construir por
defecto
ningún objeto de esa clase
1
, porque el
constructor de lambda
no es accesible
(aparte de los constructores copiar / mover)
.
Los únicos constructores garantizados por un tipo
lambda
son
los
constructores de copia / movimiento
predeterminados
.
Y no hay constructor predeterminado
El tipo de cierre asociado con una expresión lambda no tiene un constructor predeterminado y un operador de asignación de copia eliminado. Tiene un constructor de copia predeterminado y un constructor de movimiento predeterminado ([class.copy]). [Nota: Estas funciones miembro especiales se definen implícitamente como de costumbre y, por lo tanto, pueden definirse como eliminadas. - nota final]
o de cppreference :
//ClosureType() = delete; //(until C++14)
ClosureType(const ClosureType& ) = default; //(since C++14)
ClosureType(ClosureType&& ) = default; //(since C++14)
La historia de que el constructor lambda es inaccesible se remonta a su propuesta inicial, que se encuentra here
En la sección 3, segundo párrafo, y cito:
En esta traducción,
__some_unique_name
es un nombre nuevo, que no se usa en ninguna otra parte del programa de manera que pueda causar conflictos con su uso como tipo de cierre. Este nombre y el constructor de la clase no necesitan exponerse al usuario: las únicas características en las que el usuario puede confiar en el tipo de cierre son un constructor de copia (y un constructor de movimiento si se aprueba esa propuesta) y operador de llamada de función. Los tipos de cierre no necesitan constructores predeterminados, operadores de asignación o cualquier otro medio de acceso más allá de las llamadas a funciones. Puede valer la pena que la implementabilidad prohíba la creación de clases derivadas de tipos de cierre. ...
Como puede ver, la propuesta incluso sugirió que la creación de clases derivadas de tipos de cierre debería estar prohibida.
1,
por supuesto, puede copiar-inicializar la clase base con
a
para inicializar un objeto de tipo
B
Ver
this
Ahora, a tu pregunta:
¿Puede esto ser útil de alguna manera?
No en tu forma exacta.
La suya solo será instanciable con la instancia
a
.
Sin embargo, si hereda de una Clase invocable genérica, como un tipo lambda, hay dos casos en los que puedo pensar.
-
Cree un Functor que llame a un grupo de functores en una secuencia de herencia dada:
Un ejemplo simplificado:
template<typename TFirst, typename... TRemaining> class FunctionSequence : public TFirst, FunctionSequence<TRemaining...> { public: FunctionSequence(TFirst first, TRemaining... remaining) : TFirst(first), FunctionSequence<TRemaining...>(remaining...) {} template<typename... Args> decltype(auto) operator () (Args&&... args){ return FunctionSequence<TRemaining...>::operator() ( TFirst::operator()(std::forward<Arg>(args)...) ); } }; template<typename T> class FunctionSequence<T> : public T { public: FunctionSequence(T t) : T(t) {} using T::operator(); }; template<typename... T> auto make_functionSequence(T... t){ return FunctionSequence<T...>(t...); }
ejemplo de uso:
int main(){ //note: these lambda functions are bug ridden. Its just for simplicity here. //For correct version, see the one on coliru, read on. auto trimLeft = [](std::string& str) -> std::string& { str.erase(0, str.find_first_not_of('' '')); return str; }; auto trimRight = [](std::string& str) -> std::string& { str.erase(str.find_last_not_of('' '')+1); return str; }; auto capitalize = [](std::string& str) -> std::string& { for(auto& x : str) x = std::toupper(x); return str; }; auto trimAndCapitalize = make_functionSequence(trimLeft, trimRight, capitalize); std::string str = " what a Hullabaloo "; std::cout << "Before TrimAndCapitalize: str = /"" << str << "/"/n"; trimAndCapitalize(str); std::cout << "After TrimAndCapitalize: str = /"" << str << "/"/n"; return 0; }
salida
Before TrimAndCapitalize: str = " what a Hullabaloo " After TrimAndCapitalize: str = "WHAT A HULLABALOO"
-
Cree un Functor con un
operator()(...)
sobrecargadooperator()(...)
, sobrecargado con eloperator()(...)
todas las clases baseoperator()(...)
:- Nir Friedman ya ha dado un buen ejemplo de eso en su answer a esta pregunta.
- También he redactado un ejemplo similar y simplificado, extraído del suyo. Véalo en Coliru
- Jason Lucas también demostró sus aplicaciones prácticas en su presentación de CppCon 2014 "Polimorfismo con sindicatos" . Puede encontrar el Repo here , uno de ubicación exacta en el código fuente here (Gracias Cameron DaCamara)
Otro truco genial: dado que el tipo resultante de
make_functionSequence(...)
es una clase invocable.
Puede agregar más lambda o invocable en otro momento.
//.... As previously seen
auto trimAndCapitalize = make_functionSequence(trimLeft, trimRight, capitalize);
auto replace = [](std::string& str) -> std::string& { str.replace(0, 4, "Whaaaaat"); return str; };
//Add more Functors/lambdas to the original trimAndCapitalize
auto replaced = make_functionSequence(trimAndCapitalize, replace /*, ... */);
replaced(str2);
Encontré este código que parece ser interesante:
auto a = [](){};
class B : decltype(a)
{
};
Quiero saber que hace. ¿Puede esto ser útil de alguna manera?
En realidad, esto puede ser bastante útil, pero depende de cuán directo desee ser sobre todo el asunto. Considere el siguiente código:
#include <boost/variant.hpp>
#include <iostream>
#include <string>
#include <unordered_map>
template <class R, class T, class ... Ts>
struct Inheritor : public T, Inheritor<R, Ts...>
{
using T::operator();
using Inheritor<R, Ts...>::operator();
Inheritor(T t, Ts ... ts) : T(t), Inheritor<R, Ts...>(ts...) {}
};
template <class R, class T>
struct Inheritor<R, T> : public boost::static_visitor<R>, T
{
using T::operator();
Inheritor(T t) : T(t) {}
};
template <class R, class V, class ... T>
auto apply_visitor_inline(V& v, T ... t)
{
Inheritor<R, T...> i(t...);
return boost::apply_visitor(i, v);
}
int main()
{
boost::variant< int, std::string > u("hello world");
boost::variant< int, std::string > u2(5);
auto result = apply_visitor_inline<int64_t>(u, [] (int i) { return i;}, [] (const std::string& s) { return s.size();});
auto result2 = apply_visitor_inline<int64_t>(u2, [] (int i) { return i;}, [] (const std::string& s) { return s.size();});
std::cout << result;
std::cout << result2;
}
El fragmento de su pregunta no aparece en forma exacta en ningún lado.
Pero puede ver que los tipos de lambdas se infieren en
apply_visitor_inline
.
Luego se instancia una clase que hereda de todas estas lambdas.
¿El propósito?
Podemos combinar múltiples lambdas en una sola, con el propósito de cosas como
apply_visitor
.
Esta función espera recibir un único objeto de función que defina múltiples
operator()
y distinga entre ellos en función de la sobrecarga.
Pero a veces es más conveniente definir una lambda que opera en cada uno de los tipos que tenemos que cubrir.
En ese caso, la herencia de lambdas proporciona un mecanismo para combinar.
Obtuve la idea del visitante en línea desde aquí: https://github.com/exclipy/inline_variant_visitor , aunque no miré la implementación allí, por lo que esta implementación es mía (pero supongo que es muy similar).
Editar: el código publicado originalmente solo funcionó debido a un error en el sonido metálico.
De acuerdo con esta pregunta (
Lambdas sobrecargadas en C ++ y diferencias entre clang y gcc
), la búsqueda de
operator()
múltiples
operator()
en clases base es ambigua, y de hecho el código que publiqué originalmente no se compiló en gcc.
El nuevo código se compila en ambos y debe ser compatible.
Lamentablemente, aparentemente no hay forma de hacer una declaración de uso variable, por lo que debe usarse la recursividad.
Lambdas
son
objetos de función
debajo, con azúcar sintética adicional.
a
evalúa algo así (con el nombre
MyLambda
como un nombre aleatorio, al igual que cuando
MyLambda
un
namespace {}
- el nombre del espacio de nombres será aleatorio):
class MyLambda {
public:
void operator()() {
}
}
Entonces, cuando hereda de una lambda, lo que está haciendo es heredar de una clase / estructura anónima.
En cuanto a la utilidad, bueno, es tan útil como cualquier otra herencia. Puede tener la funcionalidad de múltiples lambdas con herencia múltiple en un objeto, puede agregarle nuevos métodos para extenderlo. No puedo pensar en ninguna aplicación real en este momento, pero estoy seguro de que hay muchas.
Consulte esta pregunta para más información.