Search code examples
c++templatesgccc++17template-argument-deduction

Can C++17's "template argument deduction" for class templates deduce local types?


P0091R3 ("Template argument deduction for class templates") was recently added to gcc trunk and can be tested on wandbox.

Something I had in mind was the possibility of using it to implement a "scope guard" in very few lines of code:

scope_guard _([]{ cout << "hi!\n" });

I tried implementing it on wandbox...

template <typename TF>
struct scope_guard : TF
{
    scope_guard(TF f) : TF{f} { }
    ~scope_guard() { (*this)(); }
};

int main() 
{
    scope_guard _{[]{}};
}

...but compilation failed with the following error:

prog.cc:6:5: error: 'scope_guard(TF)-> scope_guard<TF> [with TF = main()::<lambda()>]', declared using local type 'main()::<lambda()>', is used but never defined [-fpermissive]
     scope_guard(TF f) : TF{std::move(f)} { }
     ^~~~~~~~~~~

I then tried using a non-lambda local type, and got the same error.

int main() 
{
    struct K { void operator()() {} };
    scope_guard _{K{}};
}

Afterwards, I tried a non-local type, and it worked as expected.

struct K { void operator()() {} };

int main() 
{
    scope_guard _{K{}};
}

Is this featured designed in such a way that prevents local types from being deduced?

Or is this a defect in gcc's current implementation of the feature?


Solution

  • This is a bug in the current implementation: 77890 (NEW implies validity, as opposed to UNCONFIRMED fixed in 7.0). Being able to deduce a lambda was one of the motivating examples of the original paper, so would be quite awkward if it didn't work:

    // Virtually impossible to pass a lambda to a template class' constructor without declaring the lambda
    for_each(vi2.begin(), vi2.end(), Foo<???>([&](int i) { ...}));
    
    for_each(vi.begin(), vi.end(), Foo([&](int i) { ...})); // Now easy instead of virtually impossible
    

    We can create a really basic example:

    template <typename TF>
    struct scope_guard
    {
        scope_guard(TF ) { }
    };
    
    int main()
    {
        scope_guard _([]{});
    }
    

    This should perform overload resolution on the synthesized function set consisting of the functions:

    template <class TF> scope_guard<TF> synthesized(TF );
    template <class TF> scope_guard<TF> synthesized(scope_guard<TF> const& );
    template <class TF> scope_guard<TF> synthesized(scope_guard<TF>&& );
    

    which should pick the first overload and use that return type as the type of _, with TF being type of the lambda. This should all work.