Pull to refresh

Comments 14

Отличная статья, подход принят к сведению.

зы 2000 просмотров, 22 добавили в избранное, 0 комментариев — грустно это
Я имел наглость считать, что неплохо знаю C++, но о проблеме узнал только что. Как и о возможности при инстанцировании шаблона в аргументах шаблона указывать шаблонный тип без аргументов (sic).
В C++11 проблема решается проще.

template <class T, template<class, class...> class Container>
struct A {
 typedef Container<T> type;
};
Возможно, в C++14? В 11 стандарте что-то не припомню такого.
template <typename T, template<typename, typename> class Container>
struct A
{
  typedef Container<T, std::allocator<T> > type;
};

Можно и упростить (хотя, вряд ли это упрощение..):
template <typename T, template<typename Y, typename = std::allocator<Y> > class Container>
struct A
{
  typedef Container<T> type;
};


Мы должны заглянуть в реализацию std::vector, выяснить, какой тип аллокатора используется по умолчанию, и тогда использовать его.

Зачем заглядывать — все std контейнеры используют std::allocator.
std-контейнер был взят для примера, случай для любых контейнеров.
Возможно, я ошибаюсь, но мне казалось, что стандарт не накладывает ограничение на количество параметров шаблона по-умолчанию, например в std::vector:
template <typename T, typename A = std::allocator<T>, typename _P1 = def1, typename _P2 = def2, ...>
class vector;
— вполне может быть валидной сигнатурой vector.
Тогда, предложенный Вами способ не подойдет, т.к. заранее не известно, сколько параметров у шаблона.
Поправьте меня, если я ошибаюсь.
Вы, наверное, меня не поняли. Я имел в виду, что если уж говорить о проблеме, то зачем наводить нереальные примеры? В том плане, что std::vector и т. д. — вполне стандартизированные вещи.

template <typename T, typename A = std::allocator<T>, typename _P1 = def1, typename _P2 = def2, ...>
class vector;

Вы абсолютно правы. Конечно, предложенный способ не подойдёт.
И тем не менее, получился код, который зависит от шаблонных параметров. А если контейнер вообще не имеет шаблонных параметров? А если у него больше одного обязательного параметра? А если это вообще не класс, а массив? Шаблоны должны писаться исходя из потребностей к контейнеру. Если минимум потребностей — это возможность обойти элементы с помощью range-based for, то код будет выглядеть примерно так:

#include <vector>
#include <map>
#include <array>

template <typename C, typename = decltype(
          std::begin(std::declval<C>()), void(),
          *std::begin(std::declval<C>()), void(),
          std::end(std::declval<C>()), void(),
          std::next(std::begin(std::declval<C>())), void())>
struct BetterA
{
    typedef decltype(*std::begin(std::declval<const C>())) type;
};

void test()
{
    auto test1 = BetterA<std::vector<int>>();
    auto test2 = BetterA<std::map<int, int>>();
    auto test3 = BetterA<int[10]>();
    auto test4 = BetterA<std::array<int, 10>>();
}
У вас ус отклеилсяпример не компилируется. Точнее, сложности вызывает часть с BetterA<int[10]>.

P.S. Я так понял, вы хотели вот этого?

template <typename Cont>
struct A
{
    typedef typename Cont::value_type type;
};

template <typename T, size_t N>
struct A<T[N]>
{
	typedef T type;
};

void test()
{
    auto test1 = A<std::vector<int>>::type();
    auto test2 = A<std::map<int, int>>::type();
    auto test3 = A<int[10]>::type();
    auto test4 = A<std::array<int, 10>>::type();
}
MSVC2012 нормально компилирует. А с чего бы этому коду не компилироваться? Массив же фиксированного размера. А вот int*, естественно, компилятор не съест.

А длинный decltype в аргументах шаблона — это SFINAE для ленивых. Нет общего класса (соответственно, не нужен enable_if), но останавливает компилятор, если контейнер нельзя обойти.
Дело в том, что по стандарту, std::declval возвращает r-value ссылку на значение, в то время как std::begin принимает массив только по обычной ссылке, поэтому конструкция
std::begin(std::declval<int[10]>()) 
не компилируется.

Можно было бы написать что-то вроде
template <typename T>
const T& declval2();

template <typename C>
struct BetterA
{
    typedef typename std::decay<decltype(*std::begin(declval2<const C>()))>::type type;
};
но мой изначальный вариант мне нравится больше.

А, ну MSVC, как всегда, на своей волне :)
Решение интересное но непрактичное. Придётся всех заставить писать std::vector<boost::mpl::_1> чтобы использовать ваш класс, и это прямо скажем неочевидно для тех кто внутрь смотреть и не собирается.

Если мы знаем что контейнер это не массив а контейнер std или boost (да и вообще большинство контейнерописателей value_type не забывают затайпдефить), то можно обойтись так(если конечно архитектура проекта позволяет):

template <typename Container>
struct AA {
typedef Container type;
typedef typename Container::value_type valueType; // то что было T в вашем случае
};

можно добавить enable_if для value_type если надо…

Ну и в случае с C++11 тут вторым комментом ответ.
Sign up to leave a comment.

Articles