/*============================================================================= | |
Copyright (c) 2001-2007 Joel de Guzman | |
Distributed under the Boost Software License, Version 1.0. (See accompanying | |
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |
==============================================================================*/ | |
#ifndef PHOENIX_DETAIL_TYPE_DEDUCTION_HPP | |
#define PHOENIX_DETAIL_TYPE_DEDUCTION_HPP | |
/*============================================================================= | |
Return Type Deduction | |
[JDG Sept. 15, 2003] | |
Before C++ adopts the typeof, there is currently no way to deduce the | |
result type of an expression such as x + y. This deficiency is a major | |
problem with template metaprogramming; for example, when writing | |
forwarding functions that attempt to capture the essence of an | |
expression inside a function. Consider the std::plus<T>: | |
template <typename T> | |
struct plus : public binary_function<T, T, T> | |
{ | |
T operator()(T const& x, T const& y) const | |
{ | |
return x + y; | |
} | |
}; | |
What's wrong with this? Well, this functor does not accurately capture | |
the behavior of the plus operator. 1) It does not handle the case where | |
x and y are of different types (e.g. x is short and y is int). 2) It | |
assumes that the arguments and return type are the same (i.e. when | |
adding a short and an int, the return type ought to be an int). Due to | |
these shortcomings, std::plus<T>(x, y) is a poor substitute for x + y. | |
The case where x is short and y is int does not really expose the | |
problem. We can simply use std::plus<int> and be happy that the | |
operands x and y will simply be converted to an int. The problem | |
becomes evident when an operand is a user defined type such as bigint. | |
Here, the conversion to bigint is simply not acceptable. Even if the | |
unnecessary conversion is tolerable, in generic code, it is not always | |
possible to choose the right T type that can accomodate both x and y | |
operands. | |
To truly model the plus operator, what we need is a polymorphic functor | |
that can take arbitrary x and y operands. Here's a rough schematic: | |
struct plus | |
{ | |
template <typename X, typename Y> | |
unspecified-type | |
operator()(X const& x, Y const& y) const | |
{ | |
return x + y; | |
} | |
}; | |
Now, we can handle the case where X and Y are arbitrary types. We've | |
solved the first problem. To solve the second problem, we need some | |
form of return type deduction mechanism. If we had the typeof, it would | |
be something like: | |
template <typename X, typename Y> | |
typeof(X() + Y()) | |
operator()(X const& x, Y const& y) const | |
{ | |
return x + y; | |
} | |
Without the typeof facility, it is only possible to wrap an expression | |
such as x + y in a function or functor if we are given a hint that | |
tells us what the actual result type of such an expression is. Such a | |
hint can be in the form of a metaprogram, that, given the types of the | |
arguments, will return the result type. Example: | |
template <typename X, typename Y> | |
struct result_of_plus | |
{ | |
typedef unspecified-type type; | |
}; | |
Given a result_of_plus metaprogram, we can complete our polymorphic | |
plus functor: | |
struct plus | |
{ | |
template <typename X, typename Y> | |
typename result_of_plus<X, Y>::type | |
operator()(X const& x, Y const& y) const | |
{ | |
return x + y; | |
} | |
}; | |
The process is not automatic. We have to specialize the metaprogram for | |
specific argument types. Examples: | |
template <> | |
struct result_of_plus<short, int> | |
{ | |
typedef int type; | |
}; | |
template <typename T> | |
struct result_of_plus<std::complex<T>, std::complex<T> > | |
{ | |
typedef std::complex<T> type; | |
}; | |
To make it easier for the user, specializations are provided for common | |
types such as primitive c++ types (e.g. int, char, double, etc.), and | |
standard types (e.g. std::complex, iostream, std containers and | |
iterators). | |
To further improve the ease of use, for user defined classes, we can | |
supply a few more basic specializations through metaprogramming using | |
heuristics based on canonical operator rules (Such heuristics can be | |
found in the LL and Phoenix, for example). For example, it is rather | |
common that the result of x += y is X& or the result of x || y is a | |
bool. The client is out of luck if her classes do not follow the | |
canonical rules. She'll then have to supply her own specialization. | |
The type deduction mechanism demostrated below approaches the problem | |
not through specialization and heuristics, but through a limited form | |
of typeof mechanism. The code does not use heuristics, hence, no | |
guessing games. The code takes advantage of the fact that, in general, | |
the result type of an expression is related to one its arguments' type. | |
For example, x + y, where x has type int and y has type double, has the | |
result type double (the second operand type). Another example, x[y] | |
where x is a vector<T> and y is a std::size_t, has the result type | |
vector<T>::reference (the vector<T>'s reference type type). | |
The limited form of type deduction presented can detect common | |
relations if the result of a binary or unary operation, given arguments | |
x and y with types X and Y (respectively), is X, Y, X&, Y&, X*, Y*, X | |
const*, Y const*, bool, int, unsigned, double, container and iterator | |
elements (e.g the T, where X is: T[N], T*, vector<T>, map<T>, | |
vector<T>::iterator). More arguments/return type relationships can be | |
established if needed. | |
A set of overloaded test(T) functions capture these argument related | |
types. Each test(T) function returns a distinct type that can be used | |
to determine the exact type of an expression. | |
Consider: | |
template <typename X, typename Y> | |
x_value_type | |
test(X const&); | |
template <typename X, typename Y> | |
y_value_type | |
test(Y const&); | |
Given an expression x + y, where x is int and y is double, the call to: | |
test<int, double>(x + y) | |
will return a y_value_type. | |
Now, if we rig x_value_type and y_value_type such that both have unique | |
sizes, we can use sizeof(test<X, Y>(x + y)) to determine if the result | |
type is either X or Y. | |
For example, if: | |
sizeof(test<X, Y>(x + y)) == sizeof(y_value_type) | |
then, we know for sure that the result of x + y has type Y. | |
The same basic scheme can be used to detect more argument-dependent | |
return types where the sizeof the test(T) return type is used to index | |
through a boost::mpl vector which holds each of the corresponding | |
result types. | |
==============================================================================*/ | |
#include <boost/mpl/vector/vector20.hpp> | |
#include <boost/mpl/at.hpp> | |
#include <boost/mpl/not.hpp> | |
#include <boost/mpl/or.hpp> | |
#include <boost/mpl/and.hpp> | |
#include <boost/mpl/identity.hpp> | |
#include <boost/type_traits/remove_reference.hpp> | |
#include <boost/type_traits/add_reference.hpp> | |
#include <boost/type_traits/remove_cv.hpp> | |
#include <boost/type_traits/is_const.hpp> | |
#include <boost/type_traits/is_reference.hpp> | |
#include <boost/type_traits/is_same.hpp> | |
#include <boost/type_traits/is_array.hpp> | |
#include <boost/type_traits/is_pointer.hpp> | |
#include <boost/utility/enable_if.hpp> | |
#include <boost/static_assert.hpp> | |
#include <boost/preprocessor/cat.hpp> | |
#include <boost/spirit/home/phoenix/detail/local_reference.hpp> | |
namespace boost | |
{ | |
struct error_cant_deduce_type {}; | |
} | |
namespace boost { namespace type_deduction_detail | |
{ | |
typedef char(&bool_value_type)[1]; | |
typedef char(&int_value_type)[2]; | |
typedef char(&uint_value_type)[3]; | |
typedef char(&double_value_type)[4]; | |
typedef char(&bool_reference_type)[5]; | |
typedef char(&int_reference_type)[6]; | |
typedef char(&uint_reference_type)[7]; | |
typedef char(&double_reference_type)[8]; | |
typedef char(&x_value_type)[9]; | |
typedef char(&x_reference_type)[10]; | |
typedef char(&x_const_pointer_type)[11]; | |
typedef char(&x_pointer_type)[12]; | |
typedef char(&y_value_type)[13]; | |
typedef char(&y_reference_type)[14]; | |
typedef char(&y_const_pointer_type)[15]; | |
typedef char(&y_pointer_type)[16]; | |
typedef char(&container_reference_type)[17]; | |
typedef char(&container_const_reference_type)[18]; | |
typedef char(&container_mapped_type)[19]; | |
typedef char(&cant_deduce_type)[20]; | |
template <typename T, typename Plain = typename remove_cv<T>::type> | |
struct is_basic | |
: mpl::or_< | |
is_same<Plain, bool> | |
, is_same<Plain, int> | |
, is_same<Plain, unsigned> | |
, is_same<Plain, double> | |
> {}; | |
template <typename C> | |
struct reference_type | |
{ | |
typedef typename C::reference type; | |
}; | |
template <typename T> | |
struct reference_type<T const> | |
: reference_type<T> {}; | |
template <typename T, std::size_t N> | |
struct reference_type<T[N]> | |
{ | |
typedef T& type; | |
}; | |
template <typename T> | |
struct reference_type<T*> | |
{ | |
typedef T& type; | |
}; | |
template <typename T> | |
struct reference_type<T* const> | |
{ | |
typedef T const& type; | |
}; | |
template <typename C> | |
struct const_reference_type | |
{ | |
typedef typename C::const_reference type; | |
}; | |
template <typename C> | |
struct mapped_type | |
{ | |
typedef typename C::mapped_type type; | |
}; | |
struct asymmetric; | |
template <typename X, typename Y> | |
cant_deduce_type | |
test(...); // The black hole !!! | |
template <typename X, typename Y> | |
bool_value_type | |
test(bool const&); | |
template <typename X, typename Y> | |
int_value_type | |
test(int const&); | |
template <typename X, typename Y> | |
uint_value_type | |
test(unsigned const&); | |
template <typename X, typename Y> | |
double_value_type | |
test(double const&); | |
template <typename X, typename Y> | |
bool_reference_type | |
test(bool&); | |
template <typename X, typename Y> | |
int_reference_type | |
test(int&); | |
template <typename X, typename Y> | |
uint_reference_type | |
test(unsigned&); | |
template <typename X, typename Y> | |
double_reference_type | |
test(double&); | |
template <typename X, typename Y> | |
typename disable_if< | |
mpl::or_<is_basic<X>, is_const<X> > | |
, x_value_type | |
>::type | |
test(X const&); | |
template <typename X, typename Y> | |
typename disable_if< | |
is_basic<X> | |
, x_reference_type | |
>::type | |
test(X&); | |
template <typename X, typename Y> | |
typename disable_if< | |
mpl::or_< | |
is_basic<X> | |
, is_const<X> | |
> | |
, x_const_pointer_type | |
>::type | |
test(X const*); | |
template <typename X, typename Y> | |
x_pointer_type | |
test(X*); | |
template <typename X, typename Y> | |
typename disable_if< | |
mpl::or_< | |
is_basic<Y> | |
, is_same<Y, asymmetric> | |
, is_const<Y> | |
, is_same<X, Y> | |
> | |
, y_value_type | |
>::type | |
test(Y const&); | |
template <typename X, typename Y> | |
typename disable_if< | |
mpl::or_< | |
is_basic<Y> | |
, is_same<Y, asymmetric> | |
, is_same<X, Y> | |
> | |
, y_reference_type | |
>::type | |
test(Y&); | |
template <typename X, typename Y> | |
typename disable_if< | |
mpl::or_< | |
is_same<Y, asymmetric> | |
, is_const<Y> | |
, is_same<X, Y> | |
> | |
, y_const_pointer_type | |
>::type | |
test(Y const*); | |
template <typename X, typename Y> | |
typename disable_if< | |
mpl::or_< | |
is_same<Y, asymmetric> | |
, is_same<X, Y> | |
> | |
, y_pointer_type | |
>::type | |
test(Y*); | |
template <typename X, typename Y> | |
typename disable_if< | |
mpl::or_< | |
is_basic<typename X::value_type> | |
, is_same<typename add_reference<X>::type, typename X::reference> | |
> | |
, container_reference_type | |
>::type | |
test(typename X::reference); | |
template <typename X, typename Y, typename Z> | |
typename enable_if< | |
mpl::and_< | |
mpl::or_<is_array<X>, is_pointer<X> > | |
, mpl::not_<is_basic<Z> > | |
, mpl::not_<is_same<X, Z> > | |
> | |
, container_reference_type | |
>::type | |
test(Z&); | |
template <typename X, typename Y> | |
typename disable_if< | |
mpl::or_< | |
is_basic<typename X::value_type> | |
, is_same<typename add_reference<X>::type, typename X::const_reference> | |
> | |
, container_const_reference_type | |
>::type | |
test(typename X::const_reference); | |
template <typename X, typename Y> | |
typename disable_if< | |
is_basic<typename X::mapped_type> | |
, container_mapped_type | |
>::type | |
test(typename X::mapped_type); | |
template <typename X, typename Y> | |
struct base_result_of | |
{ | |
typedef typename phoenix::detail::unwrap_local_reference<X>::type x_type_; | |
typedef typename phoenix::detail::unwrap_local_reference<Y>::type y_type_; | |
typedef typename remove_reference<x_type_>::type x_type; | |
typedef typename remove_reference<y_type_>::type y_type; | |
typedef mpl::vector20< | |
mpl::identity<bool> | |
, mpl::identity<int> | |
, mpl::identity<unsigned> | |
, mpl::identity<double> | |
, mpl::identity<bool&> | |
, mpl::identity<int&> | |
, mpl::identity<unsigned&> | |
, mpl::identity<double&> | |
, mpl::identity<x_type> | |
, mpl::identity<x_type&> | |
, mpl::identity<x_type const*> | |
, mpl::identity<x_type*> | |
, mpl::identity<y_type> | |
, mpl::identity<y_type&> | |
, mpl::identity<y_type const*> | |
, mpl::identity<y_type*> | |
, reference_type<x_type> | |
, const_reference_type<x_type> | |
, mapped_type<x_type> | |
, mpl::identity<error_cant_deduce_type> | |
> | |
types; | |
}; | |
}} // namespace boost::type_deduction_detail | |
#define BOOST_RESULT_OF_COMMON(expr, name, Y, SYMMETRY) \ | |
struct name \ | |
{ \ | |
typedef type_deduction_detail::base_result_of<X, Y> base_type; \ | |
static typename base_type::x_type x; \ | |
static typename base_type::y_type y; \ | |
\ | |
BOOST_STATIC_CONSTANT(int, \ | |
size = sizeof( \ | |
type_deduction_detail::test< \ | |
typename base_type::x_type \ | |
, SYMMETRY \ | |
>(expr) \ | |
)); \ | |
\ | |
BOOST_STATIC_CONSTANT(int, index = (size / sizeof(char)) - 1); \ | |
\ | |
typedef typename mpl::at_c< \ | |
typename base_type::types, index>::type id; \ | |
typedef typename id::type type; \ | |
}; | |
#define BOOST_UNARY_RESULT_OF(expr, name) \ | |
template <typename X> \ | |
BOOST_RESULT_OF_COMMON(expr, name, \ | |
type_deduction_detail::asymmetric, type_deduction_detail::asymmetric) | |
#define BOOST_BINARY_RESULT_OF(expr, name) \ | |
template <typename X, typename Y> \ | |
BOOST_RESULT_OF_COMMON(expr, name, Y, typename base_type::y_type) | |
#define BOOST_ASYMMETRIC_BINARY_RESULT_OF(expr, name) \ | |
template <typename X, typename Y> \ | |
BOOST_RESULT_OF_COMMON(expr, name, Y, type_deduction_detail::asymmetric) | |
#endif |