Pregunta Manera más limpia para la creación de instancias de código condicional en la plantilla de C ++


Estoy tratando de ejecutar el siguiente código C ++:

#include <cmath>

template<typename T, bool> class ConditionalData {
};

template <typename T> class ConditionalData<T, false> {
};

template <typename T> class ConditionalData<T, true> {
private:
    T data;
public:
    void setData(T _data) { data = _data; }
};


template<bool hasdata> class A {
public:
    A() {
        ConditionalData<double,hasdata> data;
        if (hasdata) {
            data.setData(sin(cos(123.4)));
        }
    }
};


int main(int argNum, const char**argData) {
    A<false> test1;
    A<true> test2;
    return 0;
}

Básicamente, quiero implementar una clase A con plantilla en la que se ejecutan ciertas operaciones según el parámetro de la plantilla. Estas operaciones necesitan variables locales, que solo quiero asignar si es necesario. El problema que estoy teniendo aquí es que el cuerpo de la

if (hasdata) {
    data.setData(3);
}

condición también se crea una instancia para hasdata = false, que no se compila (utilizando g ++ 5.2). ¿Alguna idea de cómo hacer esto de la manera más limpia sin dividir el cuerpo de A :: A () en pedazos?

El código fuente anterior es un ejemplo mínimo que no funciona. La implementación real para A :: A () es relativamente larga, con las partes dependientes de "hasdata" distribuidas uniformemente sobre el código. Además, el "nombre de tipo T" para el que se utilizará la clase A es una clase relativamente compleja con constructores / destructores pesados, por lo que quiero que las instancias de T solo se asignen cuando hasdata = true. Finalmente, en las llamadas data.setData (...), puede haber cálculos complejos en "...", que solo deberían realizarse si es necesario.


18
2018-01-04 10:51


origen


Respuestas:


Si puede pagar C ++ 14, puede expresar las ramas condicionales como lambdas genéricas. El beneficio es que capturan las variables circundantes y la solución no requiere funciones adicionales de miembro.

template <bool> struct tag {};

template <typename T, typename F>
auto static_if(tag<true>, T t, F f) { return t; }

template <typename T, typename F>
auto static_if(tag<false>, T t, F f) { return f; }

template <bool B, typename T, typename F>
auto static_if(T t, F f) { return static_if(tag<B>{}, t, f); }

template <bool B, typename T>
auto static_if(T t) { return static_if(tag<B>{}, t, [](auto&&...){}); }

// ...

ConditionalData<int, hasdata> data;        
static_if<hasdata>([&](auto& d)
{
    d.setData(3);
})(data);

MANIFESTACIÓN


12
2018-01-04 12:20



Este es un patrón común, por lo que en realidad hay un papel para agregar constexpr_if a C ++. Si eso lo incluye en futuras versiones, le permitirá mantener su código tal como está.

template<bool hasdata> class A {
public:
    A() {
        ConditionalData<int,hasdata> data;
        constexpr_if (hasdata) {
        //^^^^^^^^^^ instead of plain if
            data.setData(3);
        }
    }
};

Por ahora, tendrá que conformarse con una de las otras respuestas.


Editar: esto se agregó a C ++ 17 y se llamó if constexpr


7
2018-01-04 11:14



En primer lugar, no necesita 3 versiones de class ConditionalData, porque bool pueden ser cualquiera de los dos true o false. Así que permítanme simplificarlo de la siguiente manera:

template<typename T, bool = false> class ConditionalData {
};                 //^^^^^^^^^^^^

template <typename T> class ConditionalData<T, true> {
private:
    T data;
public:
    void setData(T _data) { data = _data; }
};

En segundo lugar, para responder a su pregunta: cualquiera de los miembros se está enamorando de false categoría, simplemente sobrecargúelos fuera del class cuerpo de la siguiente manera:

template<bool hasdata> class A { 
public:
    A() {
        ConditionalData<int,hasdata> data;
        if (hasdata) {
            data.setData(3);
        }
    }   
};

template<> A<false>::A() {}  // Does nothing for `false` condition

5
2018-01-04 11:09



puedes definir setData para ambas ramas, vacía una para false condición:

template<typename T, bool> class ConditionalData {
};

template <typename T> class ConditionalData<T, false> {
    void setData(T _data) {}
};

template <typename T> class ConditionalData<T, true> {
private:
    T data;
public:
    void setData(T _data) { data = _data; }
};

template<bool hasdata> class A {
public:
    A() {
        ConditionalData<int,hasdata> data;
        data.setData(3);
    }
};

4
2018-01-04 10:59



Si no puede (o no quiere) cambiar ConditionalData, puedes crear 2 métodos en su lugar:

template<typename T>
void SetData(ConditionalData<T, false>& , const T& ) {/* Nothing */}

template<typename T>
void SetData(ConditionalData<T, true>& c, const T& value) { c.setData(value); }

y entonces

A() {
    ConditionalData<int, hasdata> data;
    SetData(data, 3);
}

Para casos más complejos

template<typename T>
void A_impl_part1(ConditionalData<T, false>&) {/* Nothing */}

template<typename T>
void A_impl_part1(ConditionalData<T, true>& c) { c.setData(3); }

y entonces

A() {
    ConditionalData<int, hasdata> data;
    A_impl_part1(data);
    // common part
    // A_impl_part2(data); // and so on
}

3
2018-01-04 11:08



Puede usar el preprocesador para 'generar' cada variación de su clase como especializaciones de plantilla.

Primero, el encabezado 'plantilla' generaremos las especializaciones de:

ATemplate.h
//no include guards to allow multiple inclusion
template<>
class A<A_HAS_DATA>
{
public:
    A()
    {
#if A_HAS_DATA
        double data;
        if (hasdata) {
            data = sin(cos(123.4));
        }
#endif
    }
}

Entonces, en realidad, generamos cada especialización para obtener un encabezado normal para usar en su código:

A.h
#pragma once

template<bool hasdata>
class A;

//Generate specialization for hasdata = true
#define A_HAS_DATA 1
#include "ATemplate.h"
#undef A_HAS_DATA
//(undef avoids redefinition warning)
//Generate specialization for hasdata = false
#define A_HAS_DATA 0
#include "ATemplate.h"
#undef A_HAS_DATA

Básicamente, en lugar de escribir manualmente cada especialización para cada caso posible (dado que puede tener múltiples configuraciones para incluir / excluir cosas), usamos el preprocesador para generar cada variante incluyendo un encabezado varias veces, cada vez con un valor diferente para el preprocesador define (s) para obtener un resultado diferente.

Prefiere utilizar los enfoques de plantilla normales cuando funcionan, pero si la cantidad de duplicación manual de código (para definir todas las variantes posibles) crece demasiado, este enfoque puede funcionar (ya que puede especificar todo 'en línea' similar a lo estático si lo haría si tuvimos uno)


2
2018-01-04 15:22