tl;dr:
As it is BOOST_AUTO_FUNCTION would have not been implementable in C++11
- The SFINAE parts would have worked without a problem
- The return-type deduction from arbitrary statements would have been impossible, due to lambdas not being allowed in unevaluated contexts
A slightly modified version of BOOST_AUTO_FUNCTION is possible, that doesn't deduce the return type from the expressions but rather another parameter.
e.g.:
template< class L, class R >
BOOST_AUTO_FUNCTION( operator -( L const& lhs, R const& rhs ),
lhs + -rhs // <- this will be used to deduce the return type
, if ( is_vector_udt< L > )
( is_vector_udt< R > )
, try ( lhs + rhs )
( -rhs )
, if typename ( L::value_type )
)
(
return lhs + -rhs
)
Here's a full implementation of how this modified BOOST_AUTO_FUNCTION would look like:
The rest of this post goes into the details of how to build BOOST_AUTO_FUNCTION in C++11 using macros without any libraries:
1. Parsing the conditions
Let's first start with a macro that can parse a single condition list from the BOOST_AUTO_FUNCTION macro (e.g. if (A)(B)(C) / try (A)(B) into something we can use in C++.
The basis for this is - as @chris metioned in the comments - concatenation. By prepending a fixed token we can convert if / try into macro names for further processing.
Such a PARSE_COND macro could look like this:
#define CONCAT_IMPL(a, b) a ## b
#define CONCAT(a,b) CONCAT_IMPL(a, b)
#define COND_if <-IF->
#define COND_try <-TRY->
#define PARSE_COND(expr) CONCAT(COND_,expr)
This already allows us to replace the leading if / try with anything we want.
Macro Expansion Examples: godbolt
PARSE_COND(if (A)(B)) => <-IF-> (A)(B)
PARSE_COND(try (A)(B)) => <-TRY-> (A)(B)
PARSE_COND(if typename (A)(B)) => <-IF-> typename (A)(B)
We can use this technique to substitute the if / try with a call to another sets of macros (HANDLE_IF / HANDLE_TRY) that will handle the next stage of processing: godbolt
#define COND_if HANDLE_IF (
#define COND_try HANDLE_TRY (
#define HANDLE_IF(expr) DOIF: expr
#define HANDLE_TRY(expr) DOTRY: expr
#define PARSE_COND(expr) CONCAT(COND_,expr))
PARSE_COND(if (A)(B)) => DOIF: (A)(B)
PARSE_COND(try (A)(B)) => DOTRY: (A)(B)
PARSE_COND(if typename (A)(B)) => DOIF: typename (A)(B)
1.1 Handling multi-token identifiers like if typename
Now we need to deal with distinguishing a plain if from if typename.
Unfortunately we can't utilize the same approach as for if / try, because concatenation needs to result in a valid preprocessor token - and concatenating something to an opening parenthesis will always produce an invalid token => we'd get a preprocessor error.
e.g. something like this would work fine for if typename, but would cause an error with just if: godbolt
#define COND_IF_typename HANDLE_IF_TYPENAME (
#define COND_IF_(...) HANDLE_IF_IF ( (__VA_ARGS__)
#define HANDLE_IF(expr) CONCAT(COND_IF_, expr))
#define HANDLE_IF_TYPENAME(expr) DOIFTYPENAME: expr
#define HANDLE_IF_IF(expr) DOIF: expr
PARSE_COND(if typename (A)(B)) => DOIFTYPENAME: (A)(B)
PARSE_COND(if (A)(B)) => <compiler error>
So we need a way to detect if there are more tokens left to be parsed (e.g. typename) or if we've already reached the parenthesized conditions.
For this we'll have to use some macro-shenanigans - it is actually possible to check if an expression starts with something parenthesized or not by using function-like macros.
If the name of a function-like macro is followed by parenthesis it will expand, otherwise the name of the macro will be left untouched.
Example: godbolt
#define CHECK(...) EXPANDED!
#define EXPANSION_CHECK(expr) CHECK expr
EXPANSION_CHECK((A)(B)) => EXPANDED!(B)
EXPANSION_CHECK(typename (A)(B)) => CHECK typename (A)(B)
By using this property of function-like macros we can write a macro that can detect if there are more tokens in a given expression: godbolt
#define EXPAND(...) __VA_ARGS__
#define EMPTY()
#define DEFER(id) id EMPTY()
#define HAS_MORE_TOKENS(expr) EXPAND(DEFER(HAS_MORE_TOKENS_RESULT)(HAS_MORE_TOKENS_CHECK expr, 0, 1))
#define HAS_MORE_TOKENS_CHECK(...) ~,~
#define HAS_MORE_TOKENS_RESULT(a, b, c, ...) c
HAS_MORE_TOKENS(typename (A)(B)) => 1
HAS_MORE_TOKENS((A)(B)) => 0
Using this we can now handle any sequence of tokens - if there are more tokens we can use the CONCAT()-trick to further expand them, and if we've reached the parenthesized conditions we can stop and know which sequence of tokens we've read before.
Example: godbolt
#define CONCAT_IMPL(a, b) a ## b
#define CONCAT(a,b) CONCAT_IMPL(a, b)
#define EXPAND(...) __VA_ARGS__
#define EMPTY()
#define DEFER(id) id EMPTY()
#define HAS_MORE_TOKENS(expr) EXPAND(DEFER(HAS_MORE_TOKENS_RESULT)(HAS_MORE_TOKENS_CHECK expr, 0, 1))
#define HAS_MORE_TOKENS_CHECK(...) ~,~
#define HAS_MORE_TOKENS_RESULT(a, b, c, ...) c
#define IIF(condition, a, b) CONCAT(IIF_, condition)(a, b)
#define IIF_0(a, b) b
#define IIF_1(a, b) a
#define COND_if HANDLE_IF (
#define COND_try HANDLE_TRY (
#define COND_IF_typename HANDLE_IF_TYPENAME (
#define HANDLE_IF(expr) IIF(HAS_MORE_TOKENS(expr), HANDLE_IF_MORE, HANDLE_IF_IF)(expr)
#define HANDLE_IF_MORE(expr) CONCAT(COND_IF_,expr))
#define HANDLE_IF_TYPENAME(expr) DOIFTYPENAME: expr
#define HANDLE_IF_IF(expr) DOIF: expr
#define HANDLE_TRY(expr) DOTRY: expr
#define PARSE_COND(expr) CONCAT(COND_,expr))
PARSE_COND(if (A)(B)) => DOIF: (A)(B)
PARSE_COND(try (A)(B)) => DOTRY: (A)(B)
PARSE_COND(if typename (A)(B)) => DOIFTYPENAME: (A)(B)
1.2 Building SFINAE expressions
Next we need to convert the actual expressions into valid C++ SFINAE code for the 3 different types of checks.
We'll inject the checks into the return-type, e.g.:
template<class L, class R>
auto operator+(L const&, R const&) -> decltype(<check A>, <check B>, <actual deduced return type>) {
/* ... */
}
- For
if we can use std::enable_if:
expr -> typename std::enable_if<expr::value>::type()
This will result in void() if expr is true, and produce a substitution error if expr is false
- For
try we can just leave the expression as it is.
- For
if typename we can use std::declval:
expr -> std::declval<typename expr>()
We need to use std::declval because expr might not be default-constructible.
So with a small foreach macro we can now convert all 3 types of SFINAE-conditions into C++-Code: godbolt
#define SEQ_HEAD(seq) EXPAND(DEFER(SEQ_HEAD_IMPL)(SEQ_HEAD_EL seq))
#define SEQ_HEAD_EL(el) el,
#define SEQ_HEAD_IMPL(head, tail) head
#define SEQ_TAIL(seq) SEQ_TAIL_IMPL seq
#define SEQ_TAIL_IMPL(el)
#define FOR_EACH(func, seq) IIF(HAS_MORE_TOKENS(seq), FOR_EACH_END, FOR_EACH_0)(func, seq)
#define FOR_EACH_END(...)
#define FOR_EACH_0(func, seq) func(SEQ_HEAD(seq)) IIF(HAS_MORE_TOKENS(SEQ_TAIL(seq)), FOR_EACH_END, FOR_EACH_1)(func, SEQ_TAIL(seq))
#define FOR_EACH_1(func, seq) func(SEQ_HEAD(seq)) IIF(HAS_MORE_TOKENS(SEQ_TAIL(seq)), FOR_EACH_END, FOR_EACH_2)(func, SEQ_TAIL(seq))
#define FOR_EACH_2(func, seq) func(SEQ_HEAD(seq)) IIF(HAS_MORE_TOKENS(SEQ_TAIL(seq)), FOR_EACH_END, FOR_EACH_3)(func, SEQ_TAIL(seq))
#define FOR_EACH_3(func, seq) func(SEQ_HEAD(seq)) IIF(HAS_MORE_TOKENS(SEQ_TAIL(seq)), FOR_EACH_END, FOR_EACH_4)(func, SEQ_TAIL(seq))
#define FOR_EACH_4(func, seq) func(SEQ_HEAD(seq)) IIF(HAS_MORE_TOKENS(SEQ_TAIL(seq)), FOR_EACH_END, FOR_EACH_END)(func, SEQ_TAIL(seq))
#define HANDLE_IF_TYPENAME(expr) FOR_EACH(HANDLE_IF_TYPENAME_IMPL, expr)
#define HANDLE_IF_TYPENAME_IMPL(expr) std::declval<typename expr>(),
#define HANDLE_IF_IF(expr) FOR_EACH(HANDLE_IF_IF_IMPL, expr)
#define HANDLE_IF_IF_IMPL(expr) typename std::enable_if<expr::value>::type(),
#define HANDLE_TRY(expr) FOR_EACH(HANDLE_TRY_IMPL, expr)
#define HANDLE_TRY_IMPL(expr) expr,
PARSE_COND(if (A)(B)) => typename std::enable_if<A::value>::type(), typename std::enable_if<B::value>::type(),
PARSE_COND(try (A)(B)) => A, B,
PARSE_COND(if typename (A)(B)) => std::declval<typename A>(), std::declval<typename B>(),
2. Building BOOST_AUTO_FUNCTION
Now that we can parse the conditions, we almost have everything we need to get an actual implementation of BOOST_AUTO_FUNCTION.
We just need another FOR_EACH implementation to loop over the different condition lists passed to BOOST_AUTO_FUNCTION, and a few more macros:
#define FOR_EACH_I(func, seq) IIF(HAS_MORE_TOKENS(seq), FOR_EACH_I_END, FOR_EACH_I_0)(func, seq)
#define FOR_EACH_I_END(...)
#define FOR_EACH_I_0(func, seq) func(SEQ_HEAD(seq)) IIF(HAS_MORE_TOKENS(SEQ_TAIL(seq)), FOR_EACH_I_END, FOR_EACH_I_1)(func, SEQ_TAIL(seq))
#define FOR_EACH_I_1(func, seq) func(SEQ_HEAD(seq)) IIF(HAS_MORE_TOKENS(SEQ_TAIL(seq)), FOR_EACH_I_END, FOR_EACH_I_2)(func, SEQ_TAIL(seq))
#define FOR_EACH_I_2(func, seq) func(SEQ_HEAD(seq)) IIF(HAS_MORE_TOKENS(SEQ_TAIL(seq)), FOR_EACH_I_END, FOR_EACH_I_3)(func, SEQ_TAIL(seq))
#define FOR_EACH_I_3(func, seq) func(SEQ_HEAD(seq)) IIF(HAS_MORE_TOKENS(SEQ_TAIL(seq)), FOR_EACH_I_END, FOR_EACH_I_4)(func, SEQ_TAIL(seq))
#define FOR_EACH_I_4(func, seq) func(SEQ_HEAD(seq)) IIF(HAS_MORE_TOKENS(SEQ_TAIL(seq)), FOR_EACH_I_END, FOR_EACH_I_END)(func, SEQ_TAIL(seq))
#define TUP_SIZE(...) TUP_SIZE_IMPL(,##__VA_ARGS__,3,2,1,0)
#define TUP_SIZE_IMPL(a,b,c,d,e,...) e
#define TUP_TO_SEQ(...) CONCAT(TUP_TO_SEQ_, TUP_SIZE(__VA_ARGS__))(__VA_ARGS__)
#define TUP_TO_SEQ_0()
#define TUP_TO_SEQ_1(a) (a)
#define TUP_TO_SEQ_2(a,b) (a)(b)
#define TUP_TO_SEQ_3(a,b,c) (a)(b)(c)
#define HANDLE_CONDS(...) FOR_EACH_I(PARSE_COND, TUP_TO_SEQ(__VA_ARGS__))
#define INFER_RETURN_TYPE(...) ([&]() { __VA_ARGS__; })()
#define BUILD_FUNC_BODY(...) INFER_RETURN_TYPE(__VA_ARGS__)) { __VA_ARGS__; }
#define BOOST_AUTO_FUNCTION(signature, ...) auto signature -> decltype( HANDLE_CONDS(__VA_ARGS__) BUILD_FUNC_BODY
And that's already it - now we have implemented BOOST_AUTO_FUNCTION: godbolt
template< class L, class R >
BOOST_AUTO_FUNCTION( operator -( L const& lhs, R const& rhs )
, if ( is_vector_udt< L > )
( is_vector_udt< R > )
, try ( lhs + rhs )
( -rhs )
, if typename ( L::value_type )
)
(
return lhs + -rhs
)
template< class L, class R >
auto operator -( L const& lhs, R const& rhs ) -> decltype(
typename std::enable_if<is_vector_udt< L >::value>::type(),
typename std::enable_if<is_vector_udt< R >::value>::type(),
lhs + rhs,
-rhs,
std::declval<typename L::value_type>(),
([&]() { return lhs + -rhs; })()) // <- problem
{
return lhs + -rhs;
}
There's a problem though - lambdas can't be used in unevaluated contexts (in C++20 they now can be used, but only without capture-clauses).
So this unfortunately doesn't compile as it is.
There's also no easy workaround for this (at least in C++11 - in C++14 we could use automatic return type deduction) - so unfortunately as it is BOOST_AUTO_FUNCTION would not have been implementable as-is in C++11.
3. Making it work
One way you could make BOOST_AUTO_FUNCTION viable in C++11 could be by removing the last feature from your list:
- with return type deduced from the given body;
If we e.g. add an additional parameter to BOOST_AUTO_FUNCTION specifically for return-type deduction it would work:
template< class L, class R >
BOOST_AUTO_FUNCTION( operator -( L const& lhs, R const& rhs ),
lhs + -rhs // <- this will be used for return-type deduction
, if ( is_vector_udt< L > )
( is_vector_udt< R > )
, try ( lhs + rhs )
( -rhs )
, if typename ( L::value_type )
)
(
return lhs + -rhs
)
Then we just need to slightly modify our existing macros to make it work: godbolt
#define HANDLE_CONDS(...) FOR_EACH_I(PARSE_COND, TUP_TO_SEQ(__VA_ARGS__))
#define BUILD_FUNC_BODY(...) { __VA_ARGS__; }
#define BOOST_AUTO_FUNCTION(signature, return_type, ...) auto signature -> decltype( HANDLE_CONDS(__VA_ARGS__) return_type) BUILD_FUNC_BODY
And now we actually have a working BOOST_AUTO_FUNCTION implementation!
Here's the code the above example would produce: godbolt
template< class L, class R >
auto operator -( L const& lhs, R const& rhs ) -> decltype(
typename std::enable_if<is_vector_udt< L >::value>::type(),
typename std::enable_if<is_vector_udt< R >::value>::type(),
lhs + rhs,
-rhs,
std::declval<typename L::value_type>(),
lhs + -rhs // <- expression from our additional parameter
) {
return lhs + -rhs;
}
3. Full Code & Boost.PP Implementation
This is the full implementation of our slightly modified BOOST_AUTO_FUNCTION: godbolt
#define CONCAT_IMPL(a, b) a ## b
#define CONCAT(a,b) CONCAT_IMPL(a, b)
#define EXPAND(...) __VA_ARGS__
#define EMPTY()
#define DEFER(id) id EMPTY()
#define HAS_MORE_TOKENS(expr) EXPAND(DEFER(HAS_MORE_TOKENS_RESULT)(HAS_MORE_TOKENS_CHECK expr, 0, 1))
#define HAS_MORE_TOKENS_CHECK(...) ~,~
#define HAS_MORE_TOKENS_RESULT(a, b, c, ...) c
#define IIF(condition, a, b) CONCAT(IIF_, condition)(a, b)
#define IIF_0(a, b) b
#define IIF_1(a, b) a
#define SEQ_HEAD(seq) EXPAND(DEFER(SEQ_HEAD_IMPL)(SEQ_HEAD_EL seq))
#define SEQ_HEAD_EL(el) el,
#define SEQ_HEAD_IMPL(head, tail) head
#define SEQ_TAIL(seq) SEQ_TAIL_IMPL seq
#define SEQ_TAIL_IMPL(el)
#define FOR_EACH(func, seq) IIF(HAS_MORE_TOKENS(seq), FOR_EACH_END, FOR_EACH_0)(func, seq)
#define FOR_EACH_END(...)
#define FOR_EACH_0(func, seq) func(SEQ_HEAD(seq)) IIF(HAS_MORE_TOKENS(SEQ_TAIL(seq)), FOR_EACH_END, FOR_EACH_1)(func, SEQ_TAIL(seq))
#define FOR_EACH_1(func, seq) func(SEQ_HEAD(seq)) IIF(HAS_MORE_TOKENS(SEQ_TAIL(seq)), FOR_EACH_END, FOR_EACH_2)(func, SEQ_TAIL(seq))
#define FOR_EACH_2(func, seq) func(SEQ_HEAD(seq)) IIF(HAS_MORE_TOKENS(SEQ_TAIL(seq)), FOR_EACH_END, FOR_EACH_3)(func, SEQ_TAIL(seq))
#define FOR_EACH_3(func, seq) func(SEQ_HEAD(seq)) IIF(HAS_MORE_TOKENS(SEQ_TAIL(seq)), FOR_EACH_END, FOR_EACH_4)(func, SEQ_TAIL(seq))
#define FOR_EACH_4(func, seq) func(SEQ_HEAD(seq)) IIF(HAS_MORE_TOKENS(SEQ_TAIL(seq)), FOR_EACH_END, FOR_EACH_END)(func, SEQ_TAIL(seq))
#define FOR_EACH_I(func, seq) IIF(HAS_MORE_TOKENS(seq), FOR_EACH_I_END, FOR_EACH_I_0)(func, seq)
#define FOR_EACH_I_END(...)
#define FOR_EACH_I_0(func, seq) func(SEQ_HEAD(seq)) IIF(HAS_MORE_TOKENS(SEQ_TAIL(seq)), FOR_EACH_I_END, FOR_EACH_I_1)(func, SEQ_TAIL(seq))
#define FOR_EACH_I_1(func, seq) func(SEQ_HEAD(seq)) IIF(HAS_MORE_TOKENS(SEQ_TAIL(seq)), FOR_EACH_I_END, FOR_EACH_I_2)(func, SEQ_TAIL(seq))
#define FOR_EACH_I_2(func, seq) func(SEQ_HEAD(seq)) IIF(HAS_MORE_TOKENS(SEQ_TAIL(seq)), FOR_EACH_I_END, FOR_EACH_I_3)(func, SEQ_TAIL(seq))
#define FOR_EACH_I_3(func, seq) func(SEQ_HEAD(seq)) IIF(HAS_MORE_TOKENS(SEQ_TAIL(seq)), FOR_EACH_I_END, FOR_EACH_I_4)(func, SEQ_TAIL(seq))
#define FOR_EACH_I_4(func, seq) func(SEQ_HEAD(seq)) IIF(HAS_MORE_TOKENS(SEQ_TAIL(seq)), FOR_EACH_I_END, FOR_EACH_I_END)(func, SEQ_TAIL(seq))
#define TUP_SIZE(...) TUP_SIZE_IMPL(,##__VA_ARGS__,3,2,1,0)
#define TUP_SIZE_IMPL(a,b,c,d,e,...) e
#define TUP_TO_SEQ(...) CONCAT(TUP_TO_SEQ_, TUP_SIZE(__VA_ARGS__))(__VA_ARGS__)
#define TUP_TO_SEQ_0()
#define TUP_TO_SEQ_1(a) (a)
#define TUP_TO_SEQ_2(a,b) (a)(b)
#define TUP_TO_SEQ_3(a,b,c) (a)(b)(c)
#define COND_if HANDLE_IF (
#define COND_try HANDLE_TRY (
#define COND_IF_typename HANDLE_IF_TYPENAME (
#define HANDLE_IF(expr) IIF(HAS_MORE_TOKENS(expr), HANDLE_IF_MORE, HANDLE_IF_IF)(expr)
#define HANDLE_IF_MORE(expr) CONCAT(COND_IF_,expr))
#define HANDLE_IF_TYPENAME(expr) FOR_EACH(HANDLE_IF_TYPENAME_IMPL, expr)
#define HANDLE_IF_TYPENAME_IMPL(expr) std::declval<typename expr>(),
#define HANDLE_IF_IF(expr) FOR_EACH(HANDLE_IF_IF_IMPL, expr)
#define HANDLE_IF_IF_IMPL(expr) typename std::enable_if<expr::value>::type(),
#define HANDLE_TRY(expr) FOR_EACH(HANDLE_TRY_IMPL, expr)
#define HANDLE_TRY_IMPL(expr) expr,
#define PARSE_COND(expr) CONCAT(COND_,expr))
#define HANDLE_CONDS(...) FOR_EACH_I(PARSE_COND, TUP_TO_SEQ(__VA_ARGS__))
#define BUILD_FUNC_BODY(...) { __VA_ARGS__; }
#define BOOST_AUTO_FUNCTION(signature, return_type, ...) auto signature -> decltype( HANDLE_CONDS(__VA_ARGS__) return_type) BUILD_FUNC_BODY
// Usage:
template< class L, class R >
BOOST_AUTO_FUNCTION( operator -( L const& lhs, R const& rhs ),
lhs + -rhs
, if ( is_vector_udt< L > )
( is_vector_udt< R > )
, try ( lhs + rhs )
( -rhs )
, if typename ( L::value_type )
)
(
return lhs + -rhs
)
With Boost Preprocessor we could cut down on a lot of the boilerplate macro code.
This is how the same implementation would look with with boost pp: godbolt
#include <boost/preprocessor.hpp>
#define COND_if HANDLE_IF (
#define COND_try HANDLE_TRY (
#define COND_IF_typename HANDLE_IF_TYPENAME (
#define HANDLE_IF(expr) BOOST_PP_IIF(BOOST_PP_IS_BEGIN_PARENS(expr), HANDLE_IF_IF, HANDLE_IF_MORE)(expr)
#define HANDLE_IF_MORE(expr) BOOST_PP_CAT(COND_IF_,expr))
#define HANDLE_IF_TYPENAME(expr) BOOST_PP_SEQ_FOR_EACH(HANDLE_IF_TYPENAME_IMPL, ~, expr)
#define HANDLE_IF_TYPENAME_IMPL(r, _, expr) std::declval<typename expr>(),
#define HANDLE_IF_IF(expr) BOOST_PP_SEQ_FOR_EACH(HANDLE_IF_IF_IMPL, ~, expr)
#define HANDLE_IF_IF_IMPL(r, _, expr) typename std::enable_if<expr::value>::type(),
#define HANDLE_TRY(expr) BOOST_PP_SEQ_FOR_EACH(HANDLE_TRY_IMPL, ~, expr)
#define HANDLE_TRY_IMPL(r, _, expr) expr,
#define PARSE_COND(r, _, i, expr) BOOST_PP_CAT(COND_,expr))
#define TUP_TO_SEQ(...) BOOST_PP_TUPLE_TO_SEQ((__VA_ARGS__))
#define HANDLE_CONDS(...) BOOST_PP_SEQ_FOR_EACH_I( PARSE_COND, ~, TUP_TO_SEQ(__VA_ARGS__))
#define BUILD_FUNC_BODY(...) { __VA_ARGS__; }
#define BOOST_AUTO_FUNCTION(signature, return_type, ...) auto signature -> decltype( HANDLE_CONDS(__VA_ARGS__) return_type) BUILD_FUNC_BODY
4. Additional resources