Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Templated Using with Nested Template

The following code does not work because the inferred template parameter F is std::tuple, whereas I want it to be Foo - the former takes two template parameters and the latter takes one.

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

Is there any way to make type inference work with the using statement rather than turning Foo into it's own class?

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

More Info

I am using C++17's std::variant along with using to alias types that are generic on a single type and I would prefer to declare these with using statements rather than creating wrapper classes for each one. Something like this:

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

Building a Haskell-Style Functor

The point of this exercise is to build a small functor library loosely based on Haskell's functor typeclass. I have defined the "typeclass" like this:

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

But I also wanted to add some sugar so that you can create a general mapper that will map a function over any function type without pre-specifying the functor type:

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

This works well with a simple value-wrapper functor:

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"
}

Now I am trying to get it to work with a more complicated type that uses std::tuple or std::variant like in the above example.

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

Based on the response by SkepticalEmpiricist below, I am thinking that type aliases may not be the way to go here and instead I will have to introduce small wrapper classes - unless there is an SFINAE approach that would get this working.

This library is mostly a curiosity and a means for me to explore some more advanced template concepts - thanks for the help!

like image 407
Taylor Avatar asked Oct 16 '22 14:10

Taylor


1 Answers

So first attempt before we start digging for some SFINAE based trickery to try circumvent the unavoidable:

Alias templates are never deduced by template argument deduction

We could "deduce" the template arguments for the compiler ourselves like this:

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

So now we have it compiling for your foo(Foo<std::string>()); call with Foo being the alias template over std::tuple and, more importantly, foo() is still specialized only for Foo<std::string>.

However, to support usage simultaneously of foo() for both the std::tuple alias template and the wrapper class for example, we still don't have it compiling error-free. As in, if we now comment-out the tuple-flavor Foo and bring back in the wrapper class Foo then calling our rewritten foo() will not compile.

To address the issue, let's give it a try with SFINAE to the rescue and replace the last declaration of foo() with this code:

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) {}

Now you can call foo() for instances of both wrapper class of tuples and alias template for tuples. You could implement in the same fashion for std::variant as well.

like image 145
Geezer Avatar answered Oct 20 '22 20:10

Geezer