Saltar a contenido

💡 Resumen:Cómo funciona std::function por dentro (borrado de tipos, optimización de búfer pequeño) con ejemplos, y cuándo std::move_only_function de C++23 sustituye a std::function para invocables solo-movibles.

Última actualización:2026-10-24

std::function en C++ explicado en detalle

Hace poco, escribiendo código C++, usé is_function y aproveché para echar un vistazo a la implementación en el código fuente; descubrí algunas cosas. Antes aprendimos que eran tres puntos ..., pero ¿sabes qué son seis puntos ......?

Del mismo modo, si hay un fragmento de código como este:

template<typename... Args>
struct foo(Args......);

¿Sabes qué significa?

¡Vamos a profundizar en los detalles juntos!

1. Los seis puntos

A los seis puntos solemos llamarlos two ellipsis operators; podemos dividirlos, y así obtenemos los tres casos siguientes

template <typename... Args> void foo1(Args......)
template <typename... Args> void foo2(Args... ...)
template <typename... Args> void foo3(Args..., ...)

Ahora está más claro; veamos el código de std::is_function.

2. Detalles de implementación de is_function

La implementación de std::is_function implica una plantilla principal y dos especializaciones parciales:

// 主模板,假设提供的类型不是函数类型
template<typename T>
struct is_function : std::false_type {};

// 偏特化,用于正常的函数类型
template<typename Ret, typename... Args>
struct is_function<Ret(Args...)> : std::true_type {};

// 偏特化,用于变参函数类型
template<typename Ret, typename... Args>
struct is_function<Ret(Args......)> : std::true_type {};
  • La plantilla principal asume que el tipo proporcionado no es un tipo función y devuelve std::false_type como resultado.
  • La primera especialización parcial es para tipos función normales, donde Ret representa el tipo de retorno y Args... los tipos de los parámetros. Si el tipo proporcionado coincide con esa firma de función, std::true_type es el resultado.
  • La segunda especialización parcial es para tipos función variádicos, donde los puntos suspensivos indican que la función acepta un número variable de parámetros. Igualmente, si el tipo proporcionado coincide con esa firma de función, std::true_type es el resultado.

3. Ejemplo de uso

Para terminar, un ejemplo de uso de std::is_function:

#include <iostream>
#include <type_traits>

int func(int x, int y) { return x + y; }
int vararg_func(int x, ...) { return x; }

int main() {
    std::cout << std::boolalpha;
    std::cout << "Is func a function? " << std::is_function<decltype(func)>::value << std::endl;
    std::cout << "Is vararg_func a function? " << std::is_function<decltype(vararg_func)>::value << std::endl;
    return 0;
}

Esto imprimirá:

Is func a function? true
Is vararg_func a function? true

¿Lo has aprendido? Fin de esta sección~

C++23 std::move_only_function vs std::function

El borrado de tipos que acabas de leer tiene un precio oculto: std::function exige que el invocable envuelto sea copiable, porque un envoltorio copiable es parte de su contrato. Captura un std::unique_ptr en una lambda e intenta almacenarla — el compilador se niega:

auto make_sink() {
    auto ptr = std::make_unique<std::vector<int>>(42);
    return [ptr = std::move(ptr)] { return ptr->size(); };  // move-only closure
}

// std::function<std::size_t()> f = make_sink();   // ERROR: closure not copyable
std::move_only_function<std::size_t()> g = make_sink();  // OK since C++23

std::move_only_function (C++23, cabecera ) elimina el requisito de copia. Acepta closures solo-movibles, así que es el tipo natural para pipelines, tareas asíncronas y callbacks que poseen recursos exclusivos como handles de fichero o conexiones.

Cómo elegir: conserva std::function cuando los invocables sean baratos de copiar y las APIs legadas lo exijan; pasa a std::move_only_function cuando la closure posee algo único. Recuerda además que std::function copia la closure al asignarla — con capturas pesadas es un coste real que move_only_function evita. En puntos sensibles al rendimiento, un parámetro de plantilla simple sigue ganando a ambos: sin borrado, sin indirección, totalmente inlineable.

Preguntas frecuentes

¿Qué diferencia hay entre std::function y std::move_only_function?

std::function exige invocables copiables y los copia al asignar; std::move_only_function (C++23) acepta invocables solo-movibles y solo los mueve. El comportamiento y la sintaxis de llamada son iguales.

¿Por qué std::function no puede contener una lambda que captura unique_ptr?

Porque std::function debe poder copiar el invocable almacenado, y el constructor de copia de la closure se elimina en cuanto captura un tipo solo-movible como unique_ptr. Usa std::move_only_function o un parámetro de plantilla.

¿Es std::move_only_function más rápida que std::function?

Puede ahorrarse la copia y algo de contabilidad, pero ambas pasan por borrado de tipos y una llamada indirecta. Si el punto de llamada conoce el tipo, toma el invocable como parámetro de plantilla: esa es la vía realmente rápida.

Temas relacionados

Comentarios