Boost logo

Boost :

Subject: Re: [boost] [offtopic] C++11 useful trick
From: Eric Niebler (eric_at_[hidden])
Date: 2012-07-02 13:29:09


On 7/2/2012 1:12 AM, Roland Bock wrote:
> On 2012-07-02 01:20, Eric Niebler wrote:
>> I recently ran into a limitation in recursive functions that make use of
>> decltype in trailing return types. I've come up with a work-around that
>> I think is a little non-obvious, so I thought I'd share.
>>
>> The problem came up while trying to implementing something like a
>> variadic back() function. The "obvious" implementation doesn't work:
>>
>> template<typename T>
>> T back(T && t)
>> {
>> return t;
>> }
>>
>> template<typename T, typename ...U>
>> auto back(T && t, U &&... u)
>> -> decltype(::back(static_cast<U &&>(u)...)) // ERROR
>> {
>> return ::back(static_cast<U &&>(u)...);
>> }
>>
>> int main()
>> {
>> int i = ::back(1,2,3);
>> }
>>
>> The problem occurs on the line marked "HERE". Trouble is, the variadic
>> "back" function is not yet in scope until after the return type.
>> However, we'd like to use it when specifying the return type.
>>
>> The solution uses a default function template parameter to make the
>> ::back symbol dependent, thereby deferring it's lookup until phase 2,
>> when the symbol is visible. Check it:
>>
>> struct S
>> {
>> template<typename T>
>> static T back(T && t)
>> {
>> return t;
>> }
>>
>> template<typename T, typename ...U, typename Impl = S>
>> static auto back(T && t, U &&... u)
>> -> decltype(Impl::back(static_cast<U &&>(u)...))
>> {
>> return Impl::back(static_cast<U &&>(u)...);
>> }
>> };
>>
>> int main()
>> {
>> int i = S::back(1,2,3);
>> }
>>
>> The trick is the "typename Impl = S" followed by a (now dependent)
>> invocation of Impl::back. Voila.
>>
>> Apologies if this is a known technique, and I'm spamming the list with
>> old news. It's news to me.
>>
>
> Hmm, I would have done something like this:
>
> // -----------------------------------------------------------------
> namespace detail
> {
> template<typename T, typename ...U>
> struct back_t
> {
> typedef typename back_t<U...>::type type;
> };
>
> template<typename T>
> struct back_t<T>
> {
> typedef T type;
> };
> }
>
> template<typename T>
> T back(T && t)
> {
> return t;
> }
>
> template<typename T, typename ...U>
> typename detail::back_t<U...>::type back(T && t, U &&... u)
> {
> return ::back(static_cast<U &&>(u)...);
> }
>
> int main()
> {
> int i = ::back(1,2,3);
> }
> // -----------------------------------------------------------------
>
> The advantages I see are that
>
> * nobody will be confused by the Impl parameter.
> * no need to put back() into a struct
>
> Do you see any drawbacks?

Yeah, it's booo-ring. ;-) Also, it needlessly instantiates O(N)
templates. And why write both a function template *and* a class template
when the function template alone will do? Now you have to maintain two
computations in parallel. I hate writing metafunctions to compute things
the compiler already knows. And in less trivial examples, the
metafunction wouldn't be so easy to write. BTW, only the implementation
of back needs to go in a struct. The user-visible function could fwd to it.

This is just a trivial example of the technique. I've been doing a lot
of C++11 making use of automatic type deduction via decltype+trailing
return types. This issue comes up fairly often for me, so finding a
solution to this problem was satisfying and greatly simplified my code.

BTW, you can also solve this problem by making the recursive call
unqualified and relying on ADL, but then you're using ADL and have
bigger issues.

-- 
Eric Niebler
BoostPro Computing
http://www.boostpro.com

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk