Why can't this metametafunction's template parameter be deduced?

68 views Asked by At

I have the following wrapper-maker metametafunction:

template <class ToWrap>
struct wrapper_maker
{
    template <bool>
    struct wrapper
    {
        wrapper(ToWrap& a) : a(a) { }
        ToWrap& a;
    };
};

I want to use it in this template function foo:

template <class ToWrap>
void foo(typename wrapper_maker<ToWrap>::template wrapper<true>& wrapped)
{
    cout << "foo" << endl;
}

Note: the use case is that foo already exists with different overloads, yet I want this particular overload to be called if the object is a wrapper_maker<ToWrap>::wrapper<bool> instance.

However, when I write a function that calls foo with such an object:

template <class ToWrap>
void call_foo(ToWrap& o)
{
    typedef typename wrapper_maker<ToWrap>::template wrapper<true> wrapped_t;
    wrapped_t wrapped(o);
    foo(wrapped);
}

Using it as so:

int to_wrap = 5;
call_foo(to_wrap);

I get this error:

prog.cpp: In instantiation of 'void call_foo(ToWrap&) [with ToWrap = int]':
prog.cpp:32:18:   required from here
prog.cpp:27:16: error: no matching function for call to 'foo(wrapped_t&)'
     foo(wrapped);
                ^
prog.cpp:27:16: note: candidate is:
prog.cpp:17:6: note: template<class ToWrap> void foo(typename wrapper_maker<ToWrap>::wrapper<true>&)
 void foo(typename wrapper_maker<ToWrap>::template wrapper<true>& wrapped)
      ^
prog.cpp:17:6: note:   template argument deduction/substitution failed:
prog.cpp:27:16: note:   couldn't deduce template parameter 'ToWrap'
     foo(wrapped);

Why is that?

EDIT: Further, is there any way to define a specialization of foo that will get called on instances of wrapper_maker<T>::wrapper<W>, for any possible T, without having to specify either T or W at the call-site?

1

There are 1 answers

5
Barry On BEST ANSWER

This:

template <class ToWrap>
void foo(typename wrapper_maker<ToWrap>::template wrapper<true>& wrapped)

Is a non-deduced context. Specifically, the first one in the list from [temp.deduct.type]4/5:

If a template parameter is used only in non-deduced contexts and is not explicitly specified, template argument deduction fails.

The non-deduced contexts are:
(5.1) — The nested-name-specifier of a type that was specified using a qualified-id.

You need to explicitly pass in ToWrap to make that work.

Let me provide another example as to why this may be the case. Suppose we had:

template <typename T> struct some_fn;
template <> struct some_fn<A> { using type = int; };
template <> struct some_fn<B> { using type = int; };

template <typename T>
void foo (typename some_fn<T>::type);

foo(0); // what should T be?

In general, in order to implement this, the compiler would have to know every possible specialization of some_fn - even if there were only one for which some_fn<T>::type were int.

Further, is there any way to define a specialization of foo that will get called on instances of wrapper_maker<T>::wrapper<W>, for any possible T, without having to specify either T or W at the call-site?

In general, no, for the same reasons articulated above. However, you can just add additional info to wrapper:

template <class ToWrap>
struct wrapper_maker
{
    template <bool b>
    struct wrapper
    {
        static constexpr bool value = b;
        using to_wrap = ToWrap;

        // etc.
    };
};

template <typename WrapperT>
void foo(WrapperT& wrapped) {
    // typename WrapperT::to_wrap is your T
    // WrappedT::value is your W
}