Como fazer um typedef condicional em C ++

89

Estou tentando fazer algo assim:

#include <iostream>
#include <random>

typedef int Integer;

#if sizeof(Integer) <= 4
    typedef std::mt19937     Engine;
#else
    typedef std::mt19937_64  Engine;
#endif

int main()
{
    std::cout << sizeof(Integer) << std::endl;
    return 0;
}

mas recebo este erro:

error: missing binary operator before token "("

Como posso fazer corretamente o typedef condicional?

Martin Drozdik
fonte
25
O pré-processador não sabe nada sobre sizeof, ou outras construções C ++. Ele certamente não sabe sobre coisas que você mesmo criou com typedef, como que nem sequer foi ainda analisado.
Lightness Races in Orbit
2
Você poderia usar enable_ifou conditionaldefinir condicionalmente typedefs, mas não pode usar o pré-processador para isso.
Bartek Banachewicz
1
@LightnessRacesinOrbit: O pré-processamento e a compilação são integrados no GCC, portanto, não é apenas certo que o código de processamento do software não conhece as definições de tipo criadas pelo usuário, mas é conhecido como falso no caso do GCC. O motivo pelo qual sizeofnão pode funcionar em condições de pré-processador é porque a linguagem é definida dessa forma, não por causa de como uma implementação funciona.
Eric Postpischil
1
@LightnessRacesinOrbit: As fases de tradução definem a sintaxe e a semântica, não a ordem de processamento. Por C ++ 2011 (N3092) 2.2 [lex.phases] nota 11, “As implementações devem se comportar como se essas fases separadas ocorressem, embora na prática diferentes fases possam ser dobradas juntas.” Meu ponto sobre o GCC é relevante porque demonstra que sua afirmação de que é assim que uma implementação funciona está errada. Em outras palavras, seu comentário afirma que um método específico de implementação evita isso. Mas não é a implementação que impede isso ( poderíamos fazer isso); é a definição da linguagem.
Eric Postpischil
1
@Eric: Eu não tive a intenção de reivindicar nada sobre as implementações. Certamente não mencionei nenhum em particular. Meu comentário afirmou um comportamento que está sujeito à regra de como se, assim como o seu. Não acho que estejamos realmente discordando de nada aqui - seu advogado de linguagem pode muito bem ter vindo direto do espelho. :)
Lightness Races in Orbit

Respostas:

139

Use a std::conditionalmeta-função do C ++ 11.

#include <type_traits>  //include this

typedef std::conditional<sizeof(int) <= 4,
                         std::mt19937,
                         std::mt19937_64>::type Engine;

Observe que se o tipo que você usa em sizeoffor um parâmetro de modelo, digamos T, você deve usar typenamecomo:

typedef typename std::conditional<sizeof(T) <= 4, // T is template parameter
                                  std::mt19937,
                                  std::mt19937_64>::type Engine;

Ou faça Enginedepender de Tcomo:

template<typename T>
using Engine = typename std::conditional<sizeof(T) <= 4, 
                                         std::mt19937,
                                         std::mt19937_64>::type;

Isso é flexível , porque agora você pode usá-lo como:

Engine<int>  engine1;
Engine<long> engine2;
Engine<T>    engine3; // where T could be template parameter!
Nawaz
fonte
4
+1 Pequenos detalhes: Verificar sizeof(int) <= 4talvez não seja uma forma muito portátil, já que em uma máquina Windows de 64 bits, o compilador GCC (MinGW) x64 oferece sizeof(int) = sizeof(long) = 4. A melhor maneira seria sizeof(void*) <= 4.
legends2k
@ legends2k: Você quer dizer Engine<void*> engine4;? ;-)
Nawaz
2
@Nawaz: Claro que não :) Eu quis dizer std::conditional<sizeof(void*) <= 4, std::mt19937, std::mt19937_64>no primeiro trecho de código.
legends2k
1
@ legends2k: Por que você usaria isso se eu lhe dei Engine<void*>? : P
Nawaz
@Nawaz: Haha ... isso é verdade. No entanto, achei que o OP provavelmente deveria conhecer a armadilha em detectar a arquitetura baseada no tamanho de um int:)
legends2k
35

Usando std::conditionalvocê pode fazer assim:

using Engine = std::conditional<sizeof(int) <= 4, 
                               std::mt19937, 
                               std::mt19937_64
                               >::type;

Se quiser fazer um typedef, você também pode fazer isso.

typedef std::conditional<sizeof(int) <= 4, 
                         std::mt19937, 
                         std::mt19937_64
                         >::type Engine
Rapptz
fonte
Não há necessidade de typenameaqui
gx_
@gx_ Sim, costumava colocá-lo lá trabalhando com modelos, não tipos concretos.
Rapptz
1
@LightnessRacesinOrbit Bem, eu consertei um pouco.
Rapptz
5

Se você não tiver o C ++ 11 disponível (embora pareça que sim se estiver planejando usar std::mt19937), você pode implementar a mesma coisa sem o suporte do C ++ 11 usando a Boost Metaprogramming Library (MPL) . Aqui está um exemplo compilável:

#include <boost/mpl/if.hpp>
#include <iostream>
#include <typeinfo>

namespace mpl = boost::mpl;

struct foo { };
struct bar { };

int main()
{
    typedef mpl::if_c<sizeof(int) <= 4, foo, bar>::type Engine;

    Engine a;
    std::cout << typeid(a).name() << std::endl;
}

Isso imprime o nome mutilado de foono meu sistema, pois um inttem 4 bytes aqui.

Jason R
fonte
1
Por que você não usa em if_cvez disso? Seria obrigação mais fácil de escrever (e entender): mpl::if_c<sizeof(int)<=4, foo, bar>::type. Não é?
Nawaz
1
@Nawaz: Na verdade, isso é melhor de várias maneiras. Eu tinha esquecido mpl::if_c. Atualizei o exemplo para usar essa abordagem.
Jason R