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.
No comments:
Post a Comment