В стандарте C++ 2011 появилась новая библиотека, которая удобна для использования в метапрограммировании с помощью шаблонов, содержащая различные компоненты для работы с типами данных. Объявления этих компонент находятся в заголовочном файле
<type_traits>.
Одной из таких компонент является следующая шаблонная структура
template <class... T> struct common_type; Как видно из ее объявления, она имеет переменное число шаблонных параметров или, иначе говоря, она представляет из себя
variadic template.
Член данных этой структурфы с именем
type представляет собой имя типа, который является "общим" для всех шаблонных аргументов, то есть тот тип, к которому все типовые шаблонные аргументы могут быть приведены.
Так как эта компонента имеет переменное число параметров, то общий тип выводится последовательно, сначала выводя общий тип для первых двух аргументов, затем выводится общий тип для полученного на первом шаге общего типа для первых двух аргументов и типа третьего аргумента, и т.д.
Вот определение этой компоненты, взятое из стандарта:
цитата: |
3 The nested typedef common_type::type shall be defined as follows: template <class ...T> struct common_type; template <class T> struct common_type<T> { typedef T type; }; template <class T, class U> struct common_type<T, U> { typedef decltype(true ? declval<T>() : declval<U>()) type; }; template <class T, class U, class... V> struct common_type<T, U, V...> { typedef typename common_type<typename common_type<T, U>::type, V...>::type type; }; |
|
Из этого определения видно, как последовательно выводится общий тип для заданных аргументов. То есть это происходит рекурсивно.
Об этом не следует забывать, так как порядок следования типов при использовании этой компоненты важен, и перестановка шаблонных аргументов может привести к различным результатам.
Чтобы было понятно, рассмотрим простой пример.
#include <type_traits>
#include <iostream>
#include <typeinfo>
struct Bar { int m; };
struct Foo { int m; };
struct Baz { Baz(const Bar&); Baz(const Foo&); };
int main()
{
std::cout << typeid( std::common_type<Bar, Baz>::type ).name() << std::endl;
std::cout << typeid( std::common_type<Foo, Baz>::type ).name() << std::endl;
std::cout << typeid( std::common_type<Baz,Foo, Bar>::type ).name() << std::endl;
}
В этом примере класс
Baz имеет два конструктора преобразования из
Bar и
Foo в
Baz.
Если запустить этот пример на выполнение, то общим типом будет тип
Baz. Так как сначала рассматривается общий тип для
Baz и
Foo, которым будет тип
Baz, так как тип
Foo может быть преобразован к
Baz. После этого выводится общий тип для
Baz и
Bar. И опять-таки общим типом будет
Baz по той же самой причине, что и на предыдущем шаге.
Но если в последнем предложении программы в конструкции
std::common_type переставить местами типы
Baz и
Bar, то есть если последнее предложение записать в виде
std::cout << typeid( std::common_type<Bar,Foo, Baz>::type ).name() << std::endl;
то уже получится ошибка компиляции, так как нет общего типа для
Bar и
Foo, то есть ни в одном из этих классов нет функции преобразования из одного типа в другой, и классы не связаны родственными отношениями.
Об этой особенности данной компоненты не стоит забывать, чтобы не тратить лишнее время на перекомпиляцию программы в следствии неправильной первоначальной растановки шаблонных аргументов при использовании
std::common_type..
Теперь что касается бага компилятора
MS VC++ 2010. Этот компилятор не поддерживает
variadic template, а потому реализация
std::common_type не соответствует стандарту. И, как это обычно бывает, если реализация не соответствует стандарту, то, скорей всего, она дает неверные результаты.
В этом легко убедиться, если запустить на выполнение следующий пример:
#include "stdafx.h"
#include <typeinfo>
#include <iostream>
#include <type_traits>
int _tmain(int argc, _TCHAR* argv[])
{
unsigned x = 0;
long y = 0;
std::cout << typeid( ( true ) ? x : y ).name() << std::endl;
std::cout << typeid( std::common_type<unsigned, long>::type ).name() << std::endl;
return 0;
}
Результатом работы этого примера должен был бы быть следующий вывод на консоль
цитата: |
unsigned long unsigned long |
|
То есть имена типов для обоих выражений должны совпадать. Однако действительный вывод этой программы, скомпилированной компилятором
MS VC++ 2010 отличается от ожидаемого и дает неверный результат:
Это очеивдный баг компилятора
MS VC++ 2010.
Сообщение о баге я направил в Майкрософт разработчикам компилятора.