Necesito escribir una macro que procese una lista arbitrariamente larga de cosas como (A)(B)(C). Si pudiera tomar una dependencia de Boost, usaría uno de los BOOST_PP_SEQ_ familia de macros. Desafortunadamente, no puedo, así que me quedo tratando de descubrir cómo funciona. Esto no es obvio.

¿Alguien aquí puede escribir una implementación simple e independiente de, digamos, BOOST_PP_SEQ_FOLD_LEFT para que yo vea? En particular, me gustaría transformar:

template_(class A, class B, class C)(
    requires IsFoo<A> && IsBar<B>)(
    requires IsBaz<C>)
void frobozzle(A, B, C);

Ser reescrito como:

template<class A, class B, class C,
    int dummy = 0,
    std::enable_if_t<dummy == 0 && (IsFoo<A> && IsBar<B>), int> = 0,
    std::enable_if_t<dummy == 0 && (IsBaz<C>), int> = 0>
void frobozzle(A, B, C);

Podría haber un número arbitrario de cláusulas requires, y cada una debería tener su propia enable_if_t. Lo tengo funcionando con una cláusula requires, pero agoté mi C preprocesador-fu en el proceso.

Está bien asumir un preprocesador compatible con std, ya que no necesito soporte de MSVC.

11
Eric Niebler 17 ene. 2018 a las 21:07

3 respuestas

La mejor respuesta

Si agrega un conjunto adicional de paréntesis en su sintaxis, es posible sin límite en el número de cláusulas 'requeridas', con relativamente pocas macros:

template_((class A, class B, class C)
    (requires IsFoo<A> && IsBar<B>)
    (requires IsBaz<C>)
)
void frobozzle(A, B, C);

Macros:

#define template_(...) template_impl_ADD_END(template_impl_LIST __VA_ARGS__) >

#define template_impl_ADD_END(...) template_impl_ADD_END2(__VA_ARGS__)
#define template_impl_ADD_END2(...) __VA_ARGS__ ## _END

#define template_impl_LIST(...) template<__VA_ARGS__, int dummy = 0 template_impl_LIST_1

#define template_impl_LIST_1(...) , std::enable_if_t<dummy == 0 && template_impl_REQUIRES(__VA_ARGS__), int> = 0 template_impl_LIST_2
#define template_impl_LIST_2(...) , std::enable_if_t<dummy == 0 && template_impl_REQUIRES(__VA_ARGS__), int> = 0 template_impl_LIST_1

#define template_impl_REQUIRES(...) (template_impl_REQUIRES_ ## __VA_ARGS__)
#define template_impl_REQUIRES_requires

#define template_impl_LIST_END
#define template_impl_LIST_1_END
#define template_impl_LIST_2_END

Con estas macros, el ejemplo anterior se expande a:

template <class A, class B, class C,
          int dummy = 0,
          std::enable_if_t<dummy == 0 && (IsFoo<A> && IsBar<B>), int> = 0,
          std::enable_if_t<dummy == 0 && (IsBaz<C>), int> = 0>

void frobozzle(A, B, C);

Explicación

Considere estas macros:

#define a(x) [x] b
#define b(x) [x] a

Con estos, esto:

a (1) (2) (3) (4)

Causará una 'reacción en cadena' de expansión de la siguiente manera:

a (1) (2) (3) (4)
[1] b (2) (3) (4)
[1] [2] a (3) (4)
[1] [2] [3] b (4)
[1] [2] [3] [4] a

La recursión no está permitida en el preprocesador, pero este tipo de reacción en cadena no es recursiva ya que la invocación de la macro solo ocurre después de la expansión de la anterior, no durante, ya que ( no es parte de la expansión. (Aunque, consulte https://wg21.link/cwg268)

Desafortunadamente, aunque esto se repetirá en una secuencia de (A)(B)(C), dejará un token adicional al final: el nombre de una de las dos macros utilizadas. El truco que usé para deshacerme de ese es envolver la lista completa con otra invocación de macro, que se agregará (con el operador concat ##) _END después de la expansión completa, por lo que se convertirá :

[1] [2] [3] [4] a_END

Entonces podemos simplemente deshacernos de este último token definiendo:

#define a_END
#define b_END

Si no podemos ajustar toda la lista, no hay forma de saber cuándo hemos llegado al último elemento. Lo único que sucede es que queda el último a o b sin un ( que lo sigue, lo que significa que simplemente no se expandirá, ya que a y b son macros de estilo funcional. (Y no podemos simplemente definir a y b para expandirse a nada, porque a y b ya son macros, aunque no se expandirán sin un {{ X9}}.)

¿Por qué dos macros?

Cuando intentamos provocar una reacción en cadena como la anterior, con solo una macro como esta:

#define a(x) [x] a

No funcionará

a (1) (2) (3) (4)
[1] a (2) (3) (4) // Doesn't expand further

Esto se debe a cómo funciona la 'protección de recursión (infinita)': si durante la expansión de una macro, se produce un token con el nombre de la macro que se está expandiendo, se marca como 'no expandible', lo que significa que nunca jamás expandir de nuevo. Ver http://eel.is/c++draft/cpp.rescan#2

Esto significa que el a expandido está marcado como 'no expandible', y nuestra reacción en cadena se detiene justo después del primer paso. Evitamos esto usando dos macros para evitar esta regla: a(..) no producirá ningún token con su propio nombre, sino solo con el nombre de la otra macro b. La expansión de a termina allí, antes de que b se expanda, porque todavía no hay ( después de b, ya que estamos 'dentro' de la expansión de a. Una vez realizada la expansión, y ya no estamos 'dentro' a, los tokens se vuelven a examinar y se encuentra una invocación adecuada de b: b(..). Ese producirá una ficha con el nombre de a nuevamente, pero como ya no estamos en la expansión del primer a, este no se marcará como 'no expandible' y la cadena La reacción continúa.

13
M-ou-se 18 ene. 2018 a las 17:25

Bien, aquí hay algo rápido y sucio que preparé que creo que puedes usar:

#include <iostream>

#define LIST (1)(2)(3)(4)

#define EAT2(list)
#define EAT(list) EAT2 list
#define KEEP(x) x EAT2(
#define STRINGIFY2(x) #x
#define STRINGIFY(x) STRINGIFY2(x)

#define HEAD(list) KEEP list )
#define TAIL(list) EAT(list)
int main()
{
    std::cout << STRINGIFY(HEAD(LIST)) << std::endl;
    std::cout << STRINGIFY(TAIL(LIST)) << std::endl;
}

Básicamente, debe ser complicado con la forma en que llama a las macros.
Toma por ejemplo:

HEAD((1)(2))

Se expande a

KEEP (1)(2) )

Que se expande a

1 EAT2 ((2))

Que se expande a

1

Esta no es una respuesta completa, pero creo que puede ser un punto de partida para lo que quieres hacer.

EDITAR

Ahora he descubierto cómo BOOST.PP realiza sus iteraciones y no es bonito, básicamente escribes manualmente las iteraciones hasta un tamaño máximo.

#define CONCAT2(x, y) x##y
#define CONCAT(x, y) CONCAT2(x, y)

#define SEQ_SIZE(seq) CONCAT(SEQ_SIZE_, SEQ_SIZE_0 seq)

# define SEQ_SIZE_0(_) SEQ_SIZE_1
# define SEQ_SIZE_1(_) SEQ_SIZE_2
# define SEQ_SIZE_2(_) SEQ_SIZE_3
# define SEQ_SIZE_3(_) SEQ_SIZE_4
# define SEQ_SIZE_4(_) SEQ_SIZE_5
# define SEQ_SIZE_5(_) SEQ_SIZE_6
# define SEQ_SIZE_6(_) SEQ_SIZE_7
# define SEQ_SIZE_7(_) SEQ_SIZE_8
# define SEQ_SIZE_8(_) SEQ_SIZE_9
# define SEQ_SIZE_9(_) SEQ_SIZE_10
# define SEQ_SIZE_10(_) SEQ_SIZE_11
# define SEQ_SIZE_SEQ_SIZE_0 0
# define SEQ_SIZE_SEQ_SIZE_1 1
# define SEQ_SIZE_SEQ_SIZE_2 2
# define SEQ_SIZE_SEQ_SIZE_3 3
# define SEQ_SIZE_SEQ_SIZE_4 4
# define SEQ_SIZE_SEQ_SIZE_5 5
# define SEQ_SIZE_SEQ_SIZE_6 6
# define SEQ_SIZE_SEQ_SIZE_7 7
# define SEQ_SIZE_SEQ_SIZE_8 8
# define SEQ_SIZE_SEQ_SIZE_9 9
# define SEQ_SIZE_SEQ_SIZE_10 10

#define MAKE_VAR(elem)                         \
    float CONCAT(var_, elem) = 0;

#define MAKE_LIST_0(op, list)
#define MAKE_LIST_1(op, list)  op (HEAD(list)) MAKE_LIST_0(op, TAIL(list))
#define MAKE_LIST_2(op, list)  op (HEAD(list)) MAKE_LIST_1(op, TAIL(list))
#define MAKE_LIST_3(op, list)  op (HEAD(list)) MAKE_LIST_2(op, TAIL(list))
#define MAKE_LIST_4(op, list)  op (HEAD(list)) MAKE_LIST_3(op, TAIL(list))
#define MAKE_LIST_5(op, list)  op (HEAD(list)) MAKE_LIST_4(op, TAIL(list))
#define MAKE_LIST_6(op, list)  op (HEAD(list)) MAKE_LIST_5(op, TAIL(list))
#define MAKE_LIST_7(op, list)  op (HEAD(list)) MAKE_LIST_6(op, TAIL(list))
#define MAKE_LIST_8(op, list)  op (HEAD(list)) MAKE_LIST_7(op, TAIL(list))
#define MAKE_LIST_9(op, list)  op (HEAD(list)) MAKE_LIST_8(op, TAIL(list))
#define MAKE_LIST_10(op, list)  op (HEAD(list)) MAKE_LIST_9(op, TAIL(list))

#define MAKE_LIST(op, list) CONCAT(MAKE_LIST_, SEQ_SIZE(list)) (op, list)

int main()
{
    MAKE_LIST(MAKE_VAR, LIST)
}

Ejecutar el preprocesador en esto da lo siguiente:

int main()
{
    float var_1 = 0; float var_2 = 0; float var_3 = 0; float var_4 = 0; float var_5 = 0;
}

Como se desee. Estoy seguro de que esto podría simplificarse un poco, pero espero que esto ayude.

2
SirGuy 17 ene. 2018 a las 19:24

Aquí están mis pequeñas 2 centavos:

El problema en mi recuerdo de las técnicas de metaprogramación del preprocesador utilizadas en Boost.Preprocessor es que no es posible tener una larga lista arbitraria de elementos al plegar sobre una secuencia.

Necesita tener tantas macros como iteración máxima, por lo que puede ser arbitrario pero hasta cierto máximo.

Sin embargo, me pregunto si incluso podría expandir las comas, porque generalmente esto se basa en concatenar con lo que viene después de una macro de condición de parada o una macro de próxima iteración. Y no veo cómo es posible expandir comas desde una macro concatenada ya que la concatenación ya no funcionará.

Lo que haría para este caso, si puede cambiar un poco, la API es:

#define EXPAND(...) __VA_ARGS__

#define template_(X, Y)   \
  template<EXPAND X       \
    , int dummy = 0       \
  Y                       \
>

#define requires(...) \
  COMMA() std::enable_if_t< dummy == 0 && (__VA_ARGS__) > = 0  

#define COMMA() ,

Entonces, con una API ligeramente modificada:

template_((class A, class B, class C),
    requires(IsFoo<A> && IsBar<B>)
    requires(IsBaz<C>)
)
void frobozzle(A, B, C);

Da salida a lo que se deseaba:

template<class A, class B, class C , 
  int dummy = 0 , 
  std::enable_if_t< dummy == 0 && (IsFoo<A> && IsBar<B>) > = 0 ,
  std::enable_if_t< dummy == 0 && (IsBaz<C>) > = 0 >
void frobozzle(A, B, C);

No es exactamente la API solicitada, pero la ventaja es que puede requerir la expresión con comas gracias a las macros que requieren VA_ARGS :

template_((class A, class B, class C),
    requires(IsBaseOf<B,C>)
)
int boo()

Traté de hacer un FOLD_LEFT ilimitado y no parece estar a mi alcance: D.

Lo que no probé es reproducir la misma sintaxis de entrada que proporcionó con las plantillas de expresión, lo que finalmente me parece más factible.

0
daminetreg 18 ene. 2018 a las 09:05