Monday, March 28, 2011

Right value references and confusion with constructors

One of the most exciting new feature arriving with C++2011 is the right value reference. But if you want to have exact signatures for the definitions of your overloaded generic functions, you will probably end up with a lot of boiler code. However, as N2027 explains, it is possible to use a right value reference of a type parameter to accept any type. Here is an example:

template class T, class A1>
std::shared_ptr<T>
factory(A1&& a1)
{
  return std::shared_ptr<T>(new T(std::forward<a1>(a1)));
}

For this function that is not intended to be overloaded, it is satisfying. And it comes to become very practical to make expression templates.

template <typename T, typename U>
MyExprTempl<typename std::decay<T>::type,
            typename std::decay<U>::type>
expr(T&& t, U&& u) {
return
  MyExprTempl<typename std::decay<T>::type,
              typename std::decay<U>::type>
  { std::forward<T>(t), std::forward<U>(u) };
}

Moreover, expression templates typically have members of the parameter types (here T and U). So we can use the same trick on the constructor.

template <typename T, typename U>
struct MyExprTempl {
  T t;
  U u;
  template <typename RT, typename RU>
  MyExprTempl(RT&& t, RU& ru):
    t{std::forward<RT>(t)},
    u{std::forward<RU>(u)} {}
  //...
};

The you have an expression template with perfect forwarding all the way.

But if you try this, you will get problems when constructors will have only one parameter, even if you use keyword explicit. To show the problem, we can use the example with factory. Suppose the following class definition:

template <typename T>
struct A {
  std::shared_ptr<T> a;
  A() = delete;
  A(const A&) = default;
  template <typename U>
  explicit A(U&& u):
    a{factory<T>(std::forward<U>(u))} {}
  };
  //...
  A<int> a{0};
  A<int> b{a};

The first instantiation will work. It will call A::A(U&&) with (U = int). However, the second will try to call explicitly A::A(A&). There are two choices, either A::A(U&&) (U = A&) which does not need any conversion (A& && is equivalent to A&), or A(const A&), which needs a an implicit conversion from A& to const A&. Of course the compiler will choose the one we did not want. The technique was too permissive. We wanted such a constructor:

template <typename U,
          typename = typename
          std::enable_if<std::is_explicitly_convertible
              <U&&, T>::value>::type
   >
explicit A(U&& u):
  a{factory<T>(std::forward<U>(u))} {}

Since factory, calls the construction of a T explicitly, we have to allow even explicit conversions. We should also avoid is_constructible as this type traits expects a class. Here we could have a non-class type. We have to note that we have to check if U&& is convertible to T, as U&& is always the result type of std::forward<U>, and also the same on std::forward<T> within factory, so we know that it will always be U&& that will be used for the conversion.

Thursday, September 23, 2010

Traits for testing operator parenthesis

For the development of Aurelia, I needed type traits to test for the presence of operator() (parenthesis) with given types. However, any expression using this operator will just fail the compilation. However, I found out that on GCC 4.5.0 and 4.5.1, a decltype(expr) may not fail the compilation if it appears as default value of a type parameter. Moreover, malformed type as default value is common in template meta-programming for selecting overloaded functions. From this point I can define:
# include <type_traits>
template <typename
 typename =
decltype(std::declval<T&>()(std::declval<U>()))>
std::true_type is_callable_helper(T&, U&&) {
 return 0;
}

template <typename T, typename U,
         typename =
         decltype(std::declval<const T&>()(std::declval<U>()))>
std::true_type is_callable_helper(const T&, U&&) {
 return 0;
}
However, it is not possible to define a default function, as GCC will find it ambiguous. Therefore, I define first two types with one convertible to the other. The cost of conversion will help us to give priorities for the overloading resolution.
struct try_first {};

struct try_second {
try_second(const try_first&) {}
};
Then I redefine my helper functions:
template <typename T, typename U>
std::false_type is_callable_helper(try_second, T, U) {
 return 0;
}

template <typename T, typename U,
         typename =
         decltype(std::declval<T&>()(std::declval<U>()))>
std::true_type is_callable_helper(try_first, T&, U&&) {
 return 0;
}

template <typename T, typename U,
         typename =
         decltype(std::declval<const T&>()(std::declval<U>()))>
std::true_type is_callable_helper(try_first, const T&, U&&) {
 return 0;
}
Now we can define our type traits. However we should note that GCC does not like using a decltype(expr) for inheritance, so we cannot directly use the return type. We define first the identity traits template itself.
template <typename T>
struct itself {
 typedef T type;
};

template <typename T, typename U>
struct is_callable_with:
public itself<decltype(is_callable_helper(try_first(),
                         std::declval<T>(),
                         std::declval<U>()))>::type {
};
Now, we have can use this traits template without generating compiler error, but instead generating our own errors. For instance:
struct m {
 void operator()(int) const {}
};

static_assert(is_callable_with<m, int>::value,
             "m is not callable with int");
... will not break the static assertion. However, when we remove the operator parenthesis on m, we get a nice error message.
To note that we can of course use ellipsis notations to handle variable size parameter lists. This example is simplified.