Pregunta Cómo llamar a una función de plantilla si existe, y algo más de lo contrario?


Quiero hacer algo como

template <typename T>
void foo(const T& t) {
   IF bar(t) would compile
      bar(t);
   ELSE
      baz(t);
}

Pensé que algo usando enable_if haría el trabajo aquí, dividiéndose foo en dos partes, pero parece que no puedo resolver los detalles. ¿Cuál es la forma más simple de lograr esto?


32
2017-09-06 17:23


origen


Respuestas:


Hay dos búsquedas que se hacen para el nombre bar. Una es la búsqueda no calificada en el contexto de definición de foo. La otra es la búsqueda dependiente de los argumentos en cada contexto de instanciación (pero el resultado de la búsqueda en cada contexto de instanciación no permite cambiar el comportamiento entre dos contextos de instanciación diferentes).

Para obtener el comportamiento deseado, puede ir y definir una función de respaldo en un fallback espacio de nombres que devuelve algún tipo único

namespace fallback {
  // sizeof > 1
  struct flag { char c[2]; };
  flag bar(...);
}

los bar se llamará a función si no coincide nada más porque la elipsis tiene el peor costo de conversión. Ahora, incluya esos candidatos en su función usando una directiva de fallback, así que eso fallback::bar se incluye como candidato en la llamada a bar.

Ahora, para ver si una llamada a bar resuelve su función, la llamará y verificará si el tipo de devolución es flag. El tipo de devolución de una función elegida de otro modo podría ser nulo, por lo que debe realizar algunos trucos de operador de coma para evitarlo.

namespace fallback {
  int operator,(flag, flag);

  // map everything else to void
  template<typename T> 
  void operator,(flag, T const&);

  // sizeof 1
  char operator,(int, flag);
}

Si nuestra función fue seleccionada, la invocación del operador de coma devolverá una referencia a int. Si no, o si la función seleccionada regresó void, luego la invocación regresa void en turno. Entonces la próxima invocación con flag ya que el segundo argumento devolverá un tipo que tenga un tamaño de 1 si se seleccionó nuestro respaldo, y un tamaño de mayor 1 (se usará el operador de coma incorporado porque void está en la mezcla) si se seleccionó algo más.

Comparamos el tamaño de y delegamos a una estructura.

template<bool>
struct foo_impl;

/* bar available */
template<>
struct foo_impl<true> {
  template<typename T>
  static void foo(T const &t) {
    bar(t);
  }
};

/* bar not available */
template<>
struct foo_impl<false> {
  template<typename T>
  static void foo(T const&) {
    std::cout << "not available, calling baz...";
  }
};

template <typename T>
void foo(const T& t) {
   using namespace fallback;

   foo_impl<sizeof (fallback::flag(), bar(t), fallback::flag()) != 1>
     ::foo(t);
}

Esta solución es ambigua si la función existente también tiene puntos suspensivos. Pero eso parece ser bastante improbable. Prueba usando la alternativa:

struct C { };
int main() {
  // => "not available, calling baz..."
  foo(C());
}

Y si se encuentra un candidato usando la búsqueda dependiente del argumento

struct C { };
void bar(C) {
  std::cout << "called!";
}
int main() {
  // => "called!"
  foo(C());
}

Para probar la búsqueda no calificada en el contexto de definición, definamos la siguiente función anterior foo_impl y foo (ponga la plantilla foo_impl arriba foo, entonces tienen el mismo contexto de definición)

void bar(double d) {
  std::cout << "bar(double) called!";
}

// ... foo template ...

int main() {
  // => "bar(double) called!"
  foo(12);
}

31
2017-09-06 18:49



litb te ha dado una muy buena respuesta. Sin embargo, me pregunto si, dado el contexto, no podríamos encontrar algo que sea menos genérico, sino también menos, um, elaborar?

Por ejemplo, qué tipos pueden ser T? ¿Cualquier cosa? Algunos tipos? ¿Un conjunto muy restringido sobre el que tienes control? Algunas clases que diseñas junto con la función foo? Dado lo último, podrías poner algo así como

typedef boolean<true> has_bar_func;

en los tipos y luego cambiar a diferentes foo sobrecargas basadas en eso:

template <typename T>
void foo_impl(const T& t, boolean<true> /*has_bar_func*/);
template <typename T>
void foo_impl(const T& t, boolean<false> /*has_bar_func*/);

template <typename T>
void foo(const T& t) {
  foo_impl( t, typename T::has_bar_func() );
}

Además, ¿puede el bar/baz función tiene casi cualquier firma, ¿hay un conjunto algo restringido, o solo hay una firma válida? Si es la última, la idea de repliegue (excelente) de litb, junto con una metafunción que emplea sizeofpodría ser un poco más simple. Pero esto no lo he explorado, así que es solo un pensamiento.


6
2017-09-06 19:53



Creo que la solución de litb funciona, pero es demasiado compleja. La razón es que él está introduciendo una función fallback::bar(...) que actúa como una "función de último recurso", y luego hace todo lo posible para NO llamarlo. ¿Por qué? Parece que tenemos un comportamiento perfecto para ello:

namespace fallback {
    template<typename T>
    inline void bar(T const& t, ...)
    {
        baz(t);
    }
}
template<typename T>
void foo(T const& t)
{
    using namespace fallback;
    bar(t);
}

Pero como indiqué en un comentario a la publicación original de litb, hay muchas razones por las cuales bar(t) podría no compilarse, y no estoy seguro de que esta solución maneje los mismos casos. Ciertamente fallará en una private bar::bar(T t)


3
2017-09-07 10:48



Si está dispuesto a limitarse a Visual C ++, puede usar __si_existe y __si_no_existe declaraciones.

Práctico en una pizca, pero específico de la plataforma.


2
2017-09-06 19:13



EDIT: ¡hablé demasiado pronto! la respuesta de litb muestra cómo esto realmente se puede hacer (a un posible costo de su cordura ... :-P)

Desafortunadamente, creo que el caso general de comprobar "¿Esta compilación?" Está fuera del alcance de deducción de argumento de plantilla de función + SFINAE, que es el truco habitual para estas cosas. Creo que lo mejor que puedes hacer es crear una plantilla de función de "copia de seguridad":

template <typename T>
void bar(T t) {   // "Backup" bar() template
    baz(t);
}

Y luego cambia foo() simplemente:

template <typename T>
void foo(const T& t) {
    bar(t);
}

Esto funcionará para la mayoría de los casos. Porque el bar() el tipo de parámetro de la plantilla es T, se considerará "menos especializada" cuando se compara con cualquier otra función o plantilla de función llamada bar() y por lo tanto, cederá la prioridad a esa plantilla preexistente o plantilla de función durante la resolución de sobrecarga. Excepto eso:

  • Si el preexistente bar() es en sí una plantilla de función tomando un parámetro de plantilla de tipo T, surgirá una ambigüedad porque ninguna plantilla es más especializada que la otra, y el compilador se quejará.
  • Las conversiones implícitas tampoco funcionarán, y darán lugar a problemas difíciles de diagnosticar: supongamos que hay un problema preexistente. bar(long) pero foo(123) se llama. En este caso, el compilador elegirá silenciosamente crear una instancia del "respaldo" bar() plantilla con T = int en lugar de realizar el int->long promoción, ¡aunque esta última se habría compilado y funcionaría bien!

En resumen: no hay una solución fácil y completa, y estoy seguro de que ni siquiera hay una solución completa y complicada. :(


2
2017-09-06 18:23



//default

////////////////////////////////////////// 
    template <class T>
    void foo(const T& t){
        baz(t);
    }

//specializations
//////////////////////////////////////////  

    template <>
    void foo(const specialization_1& t){
        bar(t);
    }
    ....
    template <>
    void foo(const specialization_n& t){
        bar(t);
    }

2
2017-09-06 21:01



¿No puedes usar la especialización completa aquí (o sobrecargar) en foo? Por ejemplo, tener la barra de llamadas de la plantilla de función, pero para ciertos tipos, ¿se especializa completamente para llamar a baz?


0
2017-09-06 17:32