Uso de rasgos de tipo C++11 para proporcionar implementaciones alternativas en línea


¿Es razonable el siguiente patrón de código cuando se usan rasgos en código templado donde ambas implementaciones alternativas son siempre compilables?

Leer el código parece más claro que hacer otras travesuras para compilar condicionalmente (pero entonces tal vez no estoy lo suficientemente familiarizado con esas travesuras).

template<typename T>
class X
{
    void do_something() noexcept(std::is_nothrow_copy_constructible<T>::value)
    {
        if (std::is_nothrow_copy_constructible<T>::value)
        {
            // some short code that assumes T's copy constructor won't throw
        }
        else
        {
            // some longer code with try/catch blocks and more complexity
        }
    }

    // many other methods
};

(La complejidad añadida es en parte proporcionar la fuerte garantía de excepción.)

Sé que este código trabajo, pero es razonable esperar que el compilador para eliminar la constante-false ramas y hacer inlining etc para el caso noexcept (donde mucho más simple que el otro caso)? Estoy esperando algo que sería tan eficiente en el caso noexcept como escribir el método con solo ese primer bloque como cuerpo (y viceversa, aunque estoy menos preocupado por el caso complejo).

Si esta no es la forma correcta de hacerlo, ¿puede alguien por favor iluminarme con la sintaxis recomendada?

Author: Miral, 2016-11-04

6 answers

[...] is it reasonable to expect the compiler to eliminate the constant-false branches and do inlining etc for the noexcept case (where much simpler than the other case)?

Podría ser, pero no confiaría en eso porque no puedes controlarlo.


Si desea quitar el if/else puede sfinae el tipo de retorno y limpiar el noexcept calificador.
Como ejemplo:

template<typename T>
class X {
    template<typename U = T>
    std::enable_if_t<std::is_nothrow_copy_constructible<T>::value>
    do_something()
    noexcept(true)
    {}

    template<typename U = T>
    std::enable_if_t<not std::is_nothrow_copy_constructible<T>::value>
    do_something()
    noexcept(false)
    {}
};

Los inconvenientes son que ahora tiene dos funciones miembro plantilla.
No estoy seguro de que se ajuste a sus necesidades.

Si se le permite usar características de C++17, if constexpr es probablemente el camino a seguir y ya no tiene que dividir su método en dos funciones miembro.

Otro enfoque podría basarse en tag-dispatching el noexcept ness de su tipo.
Como ejemplo:

template<typename T>
class X {
    void do_something(std::true_type)
    noexcept(true)
    {}

    void do_something(std::false_type)
    noexcept(false)
    {}

    void do_something()
    noexcept(do_something(std::is_nothrow_copy_constructible<T>{}))
    { do_something(std::is_nothrow_copy_constructible<T>{}); }
};

Sé que sfinae no es un verbo, pero para sfinae algo suena muy bien.

 21
Author: skypjack,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-07 06:31:59

Es razonable esperar que el compilador elimine las ramas constant-false y haga inlining etc para el caso noexcept [...]?

Sí. Dicho esto, la rama constant-false tiene que ser instanciada, lo que podría o no causar que el compilador instancie un montón de símbolos que no necesita (y luego necesita confiar en el enlazador para eliminarlos).

Todavía iría con las travesuras SFINAE( en realidad, envío de etiquetas), lo que se puede hacer muy fácilmente en C++11.

template<typename T>
class X
{
    void do_something() noexcept(std::is_nothrow_copy_constructible<T>::value)
    {
        do_something_impl(std::is_nothrow_copy_constructible<T>() ); 
    }

    void do_something_impl( std::true_type /* nothrow_copy_constructible */ )
    {
        // some short code that assumes T's copy constructor won't throw
    }

    void do_something_impl( std::false_type /* nothrow_copy_constructible */)
    {
        // some longer code with try/catch blocks and more complexity
    }

    // many other methods
};

Si va a comprobar nothrow_copy_constructor en todos los otros métodos, puede considerar la especialización de toda la clase:

template<typename T, class = std::is_nothrow_copy_constructible_t<T> >
class X
{
   //throw copy-ctor implementation
};

template<typename T>
class X<T, std::true_type>
{
   // noexcept copy-ctor implementation
};
 13
Author: sbabbi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-04 15:06:53

¿Es razonable esperar que el compilador elimine las ramas constant-false?

Sí, la eliminación de código muerto es una de las optimizaciones más simples.

... y hacer inlining etc para el caso noexcept?

Mi primer impulso fue responder "No, no puedes confiar en eso, ya que depende de dónde se encuentra el pase de inserción en el flujo de optimización en relación con el paso de eliminación de código muerto".

Pero al reflexionar más, no puedo vea por qué un compilador maduro con un nivel de optimización lo suficientemente alto no eliminará código muerto antes y después del paso de inserción. Así que esta expectativa también debería ser razonable.

Sin embargo, adivinar con respecto a las optimizaciones nunca es una cosa segura. Ir para la implementación simple y llegar al código que funciona correctamente. Luego mida su rendimiento y verifique si sus suposiciones eran ciertas. Si no lo estaban, re-redefinir la implementación para su situación no tomará significativamente más tiempo que si se fue por el camino garantizado desde el principio.

 8
Author: Leon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-04 08:14:03

Cada compilador maduro elimina código muerto. Cada compilador maduro detecta ramas constantes, y codifica la otra rama.

Puede crear una función con una docena de argumentos de plantilla que usa controles ingenuos if en su cuerpo y mirar el assumbly resultante not no va a haber un problema.

Si haces cosas como crear static variables o thread_local o instanciar símbolos, todos estos son más difíciles de eliminar.

La inserción es un poco más complicada, porque los compiladores tienden a renunciar a la inserción en algún momento; cuanto más complejo es el código, más probable es que el compilador se rinda antes de insertarlo.

En C++17 puede actualizar su if a la versión constexpr. Pero en C++14 y 11, tu código funcionará bien. Es más simple y más fácil de leer que las alternativas.

Es algo frágil, pero si se rompe generalmente lo hace en tiempo de compilación de una manera ruidosa.

 6
Author: Yakk - Adam Nevraumont,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-04 14:16:21

Pero es razonable esperar que el compilador elimine las ramas constantes-false

No. Todas las ramas serán evaluadas por el compilador. Puede intentar usar if constexpr desde c++17.

Lo que usted está tratando de lograr es SFINAE.

 1
Author: themagicalyang,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-04 06:27:15

Podría intentar implementar constexpr_if usted mismo. la solución de c++11 podría verse como sigue:

#include <iostream>
#include <type_traits>

template <bool V>
struct constexpr_if {
   template <class Lambda, class... Args>
   static int then(Lambda lambda, Args... args) {
      return 0;
   }
};

template <>
struct constexpr_if<true> {
   template <class Lambda, class... Args>
   static auto then(Lambda lambda, Args... args) -> decltype(lambda(args...)) {
       return lambda(args...);
   }

   static int then(...) {
       return 0;
   }
};

struct B {
   B() {}
   B(const B &) noexcept {}
   void do_something() {
      std::cout << "B::do_something()" << std::endl;
   }
};

struct C {
   C() {}
   C(const C &) noexcept {}
   void do_something_else() {
      std::cout << "C::do_something_else()" << std::endl;
   }
};

struct D {
   D() {}
   D(const D &) throw(int) {}
   void do_something_else() {
      std::cout << "D::do_something_else()" << std::endl;
   }
};

template <class T>
struct X {
   void do_something() {
      T t;
      constexpr_if<std::is_nothrow_copy_constructible<T>::value>::then([](B &b) {
         b.do_something();
      }, t);
      constexpr_if<std::is_nothrow_copy_constructible<T>::value>::then([](C &c) {
         c.do_something_else();
      }, t);
      constexpr_if<!std::is_nothrow_copy_constructible<T>::value>::then([](D &d) {
         d.do_something_else();
      }, t);
   }
};

int main() {
   X<B> x;
   x.do_something();
   X<C> xx;
   xx.do_something();
   X<D> xxx;
   xxx.do_something();
}

Salida:

B::do_something()
C::do_something_else()
D::do_something_else()
 1
Author: W.F.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-04 19:05:11