Как сделать условное определение типа в C ++

90

Я пытаюсь сделать что-то вроде этого:

#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;
}

но я получаю эту ошибку:

error: missing binary operator before token "("

Как правильно сделать условное определение типа?

Мартин Дроздик
источник
25
Препроцессор ничего не знает о sizeofдругих конструкциях C ++. Он, конечно же, не знает о вещах, которыми вы сами себя создали typedef, поскольку это еще даже не проанализировано.
Гонки легкости на орбите
2
Вы можете использовать enable_ifили conditionalдля условного определения typedef, но вы не можете использовать для этого препроцессор.
Bartek Banachewicz
1
@LightnessRacesinOrbit: предварительная обработка и компиляция интегрированы в GCC, поэтому не только не факт, что программный код обработки не знает об определениях типов, созданных пользователем, но и заведомо ложный в случае GCC. Причина, по которой sizeofневозможно работать в условиях препроцессора, заключается в том, что язык определен таким образом, а не в том, как работает реализация.
Эрик Постпищил
1
@LightnessRacesinOrbit: фазы перевода определяют синтаксис и семантику, а не порядок обработки. Согласно C ++ 2011 (N3092) 2.2 [lex.phases], примечание 11, «Реализации должны вести себя так, как если бы происходили эти отдельные фазы, хотя на практике разные фазы могут быть сложены вместе». Моя точка зрения о GCC актуальна, потому что она демонстрирует, что ваше утверждение о том, как работает реализация, неверно. Другими словами, в вашем комментарии утверждается, что этому препятствует определенный метод реализации. Но этому препятствует не реализация (мы могли бы это сделать); это определение языка.
Эрик Постпишил
1
@Eric: Я не хотел ничего заявлять о реализациях. Я, конечно, не упомянул ни одного конкретного. В моем комментарии говорится о поведении, которое подчиняется правилу «как если бы», как и ваше. Я не думаю, что мы действительно в чем-то не согласны - ваши языковые юристы с таким же успехом могли исходить прямо из зеркала. :)
Lightness Races на орбите

Ответы:

139

Используйте std::conditionalмета-функцию из C ++ 11.

#include <type_traits>  //include this

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

Обратите внимание: если тип, который вы используете, sizeofявляется параметром шаблона, скажем T, вы должны использовать его typenameкак:

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

Или сделать в Engineзависимости от T:

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

Это гибко , потому что теперь вы можете использовать его как:

Engine<int>  engine1;
Engine<long> engine2;
Engine<T>    engine3; // where T could be template parameter!
Наваз
источник
4
+1 Небольшая придирка: проверка на sizeof(int) <= 4, возможно, не очень переносимый способ, поскольку на 64-битной машине Windows компилятор GCC (MinGW) x64 дает sizeof(int) = sizeof(long) = 4. Было бы лучше sizeof(void*) <= 4.
legends2k
@ legends2k: Вы имеете в виду Engine<void*> engine4;? ;-)
Nawaz
2
@Nawaz: Конечно, нет :) Я имел std::conditional<sizeof(void*) <= 4, std::mt19937, std::mt19937_64>в виду в первом фрагменте кода.
legends2k
1
@ legends2k: Зачем вам это нужно, если я вам его предоставил Engine<void*>? : P
Nawaz
@Nawaz: Ха-ха ... это правда. Однако я подумал, что OP, вероятно, должен знать ловушку при обнаружении архитектуры на основе размера int
файла
35

Используя std::conditionalвы можете сделать это так:

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

Если вы хотите сделать typedef, вы тоже можете это сделать.

typedef std::conditional<sizeof(int) <= 4, 
                         std::mt19937, 
                         std::mt19937_64
                         >::type Engine
Раппц
источник
Там нет необходимости typenameздесь
gx_
@gx_ Ага, привык ставить туда из работы с шаблонами, а не с конкретными типами.
Rapptz
1
@LightnessRacesinOrbit Ну, немного поправил.
Rapptz
5

Если у вас нет доступного C ++ 11 (хотя, похоже, у вас есть, если вы планируете использовать std::mt19937), вы можете реализовать то же самое без поддержки C ++ 11, используя библиотеку метапрограммирования Boost (MPL) . Вот компилируемый пример:

#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;
}

Это печатает искаженное имя в fooмоей системе, так как здесь int4 байта.

Джейсон Р
источник
1
Почему бы вам не использовать if_cвместо этого? Было бы необходимо проще писать (и понимать): mpl::if_c<sizeof(int)<=4, foo, bar>::type. Не так ли?
Nawaz
1
@Nawaz: Действительно, это лучше во многих отношениях. Я забыл об этом mpl::if_c. Я обновил пример, чтобы использовать этот подход.
Джейсон Р.