C++: используйте #if std::is_fundamental‹T›::value для условной компиляции в MSVC 2010

В моем шаблоне мне нужно иметь разные части кода в зависимости от того, является ли имя типа фундаментальным типом или нет.

Компиляция этого кода дает C4067 в MSVC (неожиданные токены после директивы препроцессора - ожидается новая строка):

template <typename T>
void MyClass<T>::foo()
{
// ... some code here
#if std::is_fundamental<T>::value
    if(m_buf[j] < m_buf[idx_min])
        idx_min = j;
#else
    const ASSortable& curr = dynamic_cast<ASSortable&>(m_buf[j]);
    const ASSortable& curr_min = dynamic_cast<ASSortable&>(m_buf[idx_min]);
    // error checking removed for simplicity
    if(curr.before(curr_min))
        idx_min = j;
}
#endif

Шаблон должен работать как с примитивными, так и с моими собственными (полученными из ASSortable) типами данных, и ошибка возникает из кода создания экземпляра шаблона:

template class MyClass<char>;

Попытка изменить выражение прекомпилятора на это тоже не сработала:

#if std::is_fundamental<T>::value == true

и выдает точно такое же предупреждение.

Любые идеи, как сделать этот код без предупреждений?

Редактировать Еще одна вещь, которая приходит на ум, — преобразовать это в проверку во время выполнения и жить с предупреждением «постоянное выражение if»... Неужели нет способа сделать это элегантно в одна функция без специализаций и лишних наворотов?

Редактировать #2 Итак, способ, которым я решил эту проблему (что было очевидно, но почему-то ускользнуло от меня...), состоял в том, чтобы определить bool ASSortable::operator<(const ASSortable& _o) const {return this->before(_o);};, который выполняет свою работу и делает код чистым (еще раз).

Больше никаких if или #ifdef или подобных помех в моем коде!

Не могу поверить, что я даже задал этот вопрос, ведь на него был такой очевидный и простой ответ :(


person YePhIcK    schedule 12.07.2012    source источник
comment
Не могли бы вы использовать специализацию MyClass?   -  person Björn Pollex    schedule 12.07.2012
comment
Я стараюсь не загрязнять реализацию слишком большим количеством специализаций. Учтите, что MyClass будет специализирован для нескольких основных типов, и код слишком быстро запутается (специализация будет охватывать char/short/int/long/long long и их беззнаковые аналоги, float/double/long double), так что я d на самом деле лучше использовать условную компиляцию вместо копирования блоков кода для каждой специализации фундаментального типа   -  person YePhIcK    schedule 12.07.2012
comment
@YePhIcK Вы всегда можете использовать enable_if, чтобы предотвратить специализацию класса для одной специализированной перегрузки. Мое решение делает это.   -  person pmr    schedule 12.07.2012
comment
Все приведенные ниже решения действительны, однако я, вероятно, в конечном итоге не буду использовать ни одно из них по следующей причине: отладка программ как минимум в два раза сложнее, чем их написание, поэтому, если вы пишете программу на пределе своих возможностей.. Я верю в простой код, который не требует экспертного уровня для чтения/понимания. Жаль, что нет элегантного решения моей проблемы.   -  person YePhIcK    schedule 12.07.2012
comment
Зачем вообще определять интерфейс ASSortable? У нас уже есть operator<.   -  person Puppy    schedule 28.07.2012


Ответы (5)


Распространенным шаблоном решения этой проблемы является перемещение функции в специализированный базовый класс и злоупотребление наследованием, чтобы довести ее до вашей области:

template <typename T, bool is_fundamental>
struct Foo_impl {
   void foo() {
   }
};
template <typename T>
struct Foo_impl<T,true>
{
   void foo() {              // is fundamental version
   }
};
template <typename T>
class Foo : public Foo_impl<T, std::is_fundamental_type<T>::value> {
   // ...
};

Другой подход состоит в том, чтобы реализовать их как частные функции в вашем классе и отправлять их внутренне из foo на основе признака. Это действительно простое и более чистое решение, но оно не работает, если одна из двух версий foo_impl не скомпилируется. В этом случае вы можете использовать, как другие предложили функцию-член шаблона для разрешения, но я бы все же предложил нешаблонный foo в качестве общедоступного интерфейса, перенаправляя его в частный шаблон foo_impl. Причина в том, что template здесь является деталью реализации для взлома условной компиляции, а не частью интерфейса. Вы не хотите, чтобы пользовательский код вызывал эту функцию-член с аргументами шаблона, отличными от типа вашего собственного класса. Заимствование из ответа pmr:

template <typename T>
struct Foo
{
  template <typename U = T, 
            typename std::enable_if< 
              std::is_fundamental<U>::value, int >::type* _ = 0
           >
  void foo() {
    std::cout << "is fundamental" << std::endl;
  }
//...

Это решение позволяет использовать пользовательский код, например:

Foo<int> f;
f.foo<std::string>();

Который создаст экземпляр функции, которая вам не нужна и не нужна, и выполнит логику, которая вам не нужна. Даже если пользователи не пытаются обмануть ваш класс, тот факт, что он является шаблоном в интерфейсе, может сбить с толку и заставить пользователей думать, что его можно вызывать для разных типов.

person David Rodríguez - dribeas    schedule 12.07.2012
comment
Да, это реальная проблема с моим решением. Ваш API намного безопаснее и чище. - person pmr; 12.07.2012

Препроцессор запускается на ранней стадии компиляции, до того, как компилятор проанализирует типы и узнает значение std::is_fundamental<T>::value, поэтому он не может работать таким образом.

Вместо этого используйте специализацию:

template<bool> void f();

template<> void f<true>() {
    if(m_buf[j] < m_buf[idx_min])
        idx_min = j;
}

template<> void f<false>() {
    const ASSortable& curr = dynamic_cast<ASSortable&>(m_buf[j]);
    const ASSortable& curr_min = dynamic_cast<ASSortable&>(m_buf[idx_min]);
    // error checking removed for simplicity
    if(curr.before(curr_min))
        idx_min = j;
}

template <typename T>
void MyClass<T>::foo()
{
// ... some code here
    f<std::is_fundamental<T>::value>();
}

EDIT: вам, вероятно, потребуется сделать f функцией-членом, однако это невозможно напрямую, поскольку MyClass<T> не является специализированным шаблоном. Вы можете сделать f глобальным, который делегирует вызов правильному члену MyClass. Однако есть и другой подход.

Используя перегрузку, это становится:

void MyClass<T>::f(const true_type&) {
    if(m_buf[j] < m_buf[idx_min])
        idx_min = j;
}

void MyClass<T>::f(const false_type&) {
    const ASSortable& curr = dynamic_cast<ASSortable&>(m_buf[j]);
    const ASSortable& curr_min = dynamic_cast<ASSortable&>(m_buf[idx_min]);
    // error checking removed for simplicity
    if(curr.before(curr_min))
        idx_min = j;
}

template <typename T>
void MyClass<T>::foo()
{
// ... some code here
    f(std::is_fundamental<T>::type());
}
person Yakov Galka    schedule 12.07.2012
comment
@DavidRodríguez-dribeas: неверно, они не создаются, если вы не создаете их явно. - person Yakov Galka; 12.07.2012
comment
Извините, я не знаю, о чем я думал. - person David Rodríguez - dribeas; 12.07.2012

Вы путаете состояния компиляции. Препроцессор запускается до фактического компилятора и ничего не знает о типах или шаблонах. Он просто выполняет (очень) сложную замену текста.

В текущем C++ нет ничего подобного static if1, поэтому вам придется прибегнуть к другому методу, чтобы включить условную компиляцию. Для функций я бы предпочел enable_if.

#include <type_traits>
#include <iostream>

template <typename T>
struct Foo
{
  template <typename U = T, 
            typename std::enable_if< 
              std::is_fundamental<U>::value, int >::type = 0
           >
  void foo() {
    std::cout << "is fundamental" << std::endl;
  }

  template <typename U = T, 
            typename std::enable_if< 
              !(std::is_fundamental<U>::value), int >::type = 0
           >
  void foo() {
    std::cout << "is not fundamental" << std::endl;
  }
};


struct x {};

int main()
{
  Foo<int> f; f.foo();
  Foo<x> f2; f2.foo();
  return 0;
}

1 Ссылки:

Видео: статично, если показано Александреску в Going Native.

n3322: предложение Уолтера Э. Брауна для static if

n3329: Саттер, Брайт и Александреску предложение для static if

person pmr    schedule 12.07.2012
comment
Жаль, что static if в ближайшее время не появится :( - person YePhIcK; 12.07.2012
comment
@YePhIcK: у static if есть свои проблемы. Это позволило бы использовать определенные конструкции, которые могут сбивать с толку пользователя и трудно отслеживаются компилятором. Я не могу вспомнить подробности, но это сводится к тому, что до создания экземпляра шаблона (второй этап поиска) фактическое содержимое типа неизвестно (некоторые поля или свойства могут быть изменены операторами static if) - person David Rodríguez - dribeas; 12.07.2012

Это почти то, что он говорит, вы не можете использовать :: в директивах препроцессора. На самом деле, единственное, что вы можете использовать после #if, это выражение-константа, которое определено до времени компиляции. Некоторую информацию можно найти здесь

person SingerOfTheFall    schedule 12.07.2012

std::is_fundamental<T>::value == true нельзя использовать во время предварительной обработки. Я думаю, вам придется использовать какой-то трюк SFINAE с std::enable_if:

template <typename T>
typename std::enable_if<std::is_fundamental<T>::value, void>::type 
MyClass<T>::foo()
{
    // ... some code here

    if(m_buf[j] < m_buf[idx_min])
        idx_min = j;
}


template <typename T>
typename std::enable_if<!std::is_fundamental<T>::value, void>::type 
MyClass<T>::foo()
{
    // ... some code here

    const ASSortable& curr = dynamic_cast<ASSortable&>(m_buf[j]);
    const ASSortable& curr_min = dynamic_cast<ASSortable&>(m_buf[idx_min]);
    // error checking removed for simplicity
    if(curr.before(curr_min))
        idx_min = j;
}
person Andrzej    schedule 12.07.2012
comment
На самом деле это не сработает, если вы не сделаете foo зависимым от параметра шаблона. - person pmr; 12.07.2012