Шаблон с использованием вложенного шаблона

Следующий код не работает, потому что предполагаемый параметр шаблона F равен std::tuple, тогда как я хочу, чтобы он был Foo — первый принимает два параметра шаблона, а второй — один.

#include <tuple>

template <typename T>
using Foo = std::tuple<int, T>;

template <template <typename> class F>
void foo(F<std::string> bar) {}

void test() {
  foo(Foo<std::string>());
}

Есть ли способ заставить вывод типа работать с оператором using, а не превращать Foo в свой собственный класс?

#include <tuple>

template <typename T>
class Foo {
  std::tuple<int, T> bar;
};

template <template <typename> class F>
void foo(F<std::string> bar) {}

void test() {
  foo(Foo<std::string>());
}

Больше информации

Я использую std::variant С++ 17 вместе с псевдонимами типов, которые являются общими для одного типа, и я бы предпочел объявлять их с помощью операторов using, а не создавать классы-оболочки для каждого из них. Что-то вроде этого:

// Assuming Plus, Minus, etc all exist
template <typename T>
using Operation = std::variant<Plus<T>, Minus<T>, Times<T>>;

Создание функтора в стиле Haskell

Целью этого упражнения является создание небольшой библиотеки функторов, свободно основанной на классе типов функторов Haskell. Я определил «класс типов» следующим образом:

template <template <typename> class F>
class Functor {
public:
  template <typename T, typename U>
  static F<U> fmap(std::function<U(T)> f, F<T> functor);
};

Но я также хотел добавить немного сахара, чтобы вы могли создать общий преобразователь, который будет отображать функцию на любой тип функции без предварительного указания типа функтора:

template <typename T, typename U>
struct FMap {
  FMap(std::function<U(T)> f) : f_(f) {}

  template <template <typename> class F>
  F<U> operator()(F<T> functor) {
    return Functor<F>::fmap(f_, functor);
  }

private:
  std::function<U(T)> f_;
};

template <typename T, typename U>
FMap<T, U> fmap(std::function<U(T)> f) {
  return FMap<T, U>(f);
}

Это хорошо работает с простым функтором-оболочкой значений:

template <typename T>
class Value {
public:
  Value(T value) : value_(value) {}

  const T& value() const {
    return value_;
  }

private:
  T value_;
};

template <>
template <typename T, typename U>
Value<U> Functor<Value>::fmap(std::function<U(T)> f, Value<T> value) {
  return Value<U>(f(value.value()));
}

void test() {
  std::function<std::string(int)> fn = [](int x) {
    return std::to_string(x);
  };
  auto result = fmap(fn)(Value(42));
  // result.value() == "42"
}

Теперь я пытаюсь заставить его работать с более сложным типом, который использует std::tuple или std::variant, как в приведенном выше примере.

template <>
template <typename T, typename U>
Foo<U> Functor<Foo>::fmap(std::function<U(T)> f, Foo<T> value) {
  return Foo<U>(std::get<0>(value), f(std::get<1>(value)));
}

void test() {
  std::function<std::string(int)> fn = [](int x) {
    return std::to_string(x);
  };
  // This is the desirable syntax but it doesn't build
  // fmap(fn)(Foo<int>(42, 7));

  // This builds but it's super ugly
  fmap(fn).operator()<Foo>(Foo<int>(42, 7));
}

Основываясь на ответе SkepticalEmpiricist ниже, я думаю, что псевдонимы типов не могут быть здесь, и вместо этого мне придется ввести небольшие классы-оболочки - если только не будет подхода SFINAE, который заставит это работать.

Эта библиотека в основном представляет собой любопытство и средство для меня, чтобы изучить некоторые более продвинутые концепции шаблонов - спасибо за помощь!


person Taylor    schedule 24.08.2018    source источник


Ответы (2)


Итак, сначала попытайтесь, прежде чем мы начнем копать какие-то хитрости на основе SFINAE, попытаться обойти неизбежное:

Шаблоны псевдонимов никогда не выводятся путем вывода аргументов шаблона

Мы могли бы сами «вывести» аргументы шаблона для компилятора следующим образом:

#include <tuple>

template <typename T>
using Foo = std::tuple<int, T>;

template <template <typename ...> class F, typename T, typename ...Ts>
void foo(F<T, std::string, Ts...> bar) {}

void test() {
  foo(Foo<std::string>());
}

Итак, теперь он компилируется для вашего вызова foo(Foo<std::string>());, где Foo является шаблоном псевдонима вместо std::tuple, и, что более важно, foo() по-прежнему специализирован только для Foo<std::string>.

Однако для поддержки одновременного использования foo() как для std::tuple шаблона псевдонима, так и для класса-оболочки, например, мы по-прежнему не компилируем его без ошибок. Например, если мы сейчас закомментируем tuple-вариант Foo и вернем класс-оболочку Foo, то вызов нашего переписанного foo() не будет компилироваться.

Чтобы решить эту проблему, давайте попробуем использовать SFINAE на помощь и заменим последнее объявление foo() на этот код:

template <template <typename ...> class F, typename T, typename ...Ts,
          typename std::enable_if_t<std::is_same<F<T, Ts...>, 
              std::tuple<T, Ts...>>::value >* = nullptr>
void foo(F<T, std::string, Ts...> bar) {}

template <template <typename> class F>
void foo(F<std::string> bar) {}

Теперь вы можете вызывать foo() для экземпляров как класса-оболочки tuples, так и шаблона псевдонима для tuples. Вы можете реализовать таким же образом и для std::variant.

person Geezer    schedule 24.08.2018
comment
Я бы предпочел это - я пишу небольшую библиотеку функторов, основанную на функторе Haskell. Я добавлю еще немного контекста к своему исходному сообщению. - person Taylor; 24.08.2018
comment
@Taylor Добавлено редактирование, чтобы сохранить полное требование, как я понял из вашего вопроса, до сих пор. Дайте мне знать, где это стоит с вашей точки зрения. - person Geezer; 24.08.2018
comment
Спасибо, что копаетесь в этом. В итоге я просто выбрал класс-оболочку, потому что я думаю, что подход using может вызвать больше проблем в будущем. Было бы неплохо, если бы С++ предлагал что-то эквивалентное newtype в Haskell — простота using, но он также создает новый тип, который можно проверить с помощью компилятора вместо псевдонима. - person Taylor; 25.08.2018
comment
Пометка как ответ, потому что я не знал, что псевдонимы не используются при выводе типа, хотя теперь я понимаю, почему это так. - person Taylor; 25.08.2018

С:

template <typename T> using Foo = std::tuple<int, T>;

template <template <typename> class F> void foo(F<std::string> bar) {}

void test() { foo(Foo<std::string>()); }

Foo<std::string> is std::tuple<int, std::string>.

so test is

void test() { foo(std::tuple<int, std::string>()); }

Как вы ожидаете, что компилятор выведет из tuple, из какого псевдонима он исходит?

мы могли бы иметь

template <typename T> using Bar = std::tuple<int, std::string>;
template <typename T> using Bar2 = std::tuple<some_trait<T>::type, some_trait<T>::type2>;
// ...

Возможный обходной путь может быть:

template <typename T, typename U>
Foo<U> fmap(std::function<U(T)> f, Foo<T> value)
{
    return Foo<U>(std::get<0>(value), f(std::get<1>(value)));   
}

С синтаксисом вызова:

fmap(fn, Foo<int>(42, 7));
person Jarod42    schedule 24.08.2018