Search code examples
c++templatesc++17template-meta-programmingfunction-templates

Battling type parameter order and enable_if specifications in function templates


Consider:

template <typename InputIt, typename T = typename std::remove_const<typename InputIt::value_type>::type>
  std::vector<T> WorkOnIt( InputIt first, InputIt last)
{
    std::vector<T> result(first, last);
    ...fiddle with result...
    return result;
}
...
std::set<OneType> goods;
std::vector<OneType> cooked;
...
cooked = WorkOnIt( goods.begin(), goods.end());

This compiles and works but I'm not happy with it even though the call site is clean. If I want to explicitly specify T, I can't just WorkOnIt<CookedType> because I'd have to specify InputIt in front of that. That's ugly since that type is manifest in the argument list. And besides I just want the input iterators to cough up something that is convertible to T. Say like this:

std::set<SourceType> goods;
std::vector<CookedType> cooked;
...
cooked = WorkOnIt<CookedType>( goods.begin(), goods.end());

There must be some enable_if incantation that does what I want but I can't figure it out. (BTW, I'm stuck on C++17.)


Solution

  • You can try using two overloads: one with deduced Item parameter and another with explicit.

    #include <set>
    #include <string>
    #include <type_traits>
    #include <vector>
    #include <iostream>
    
    template <typename Item, typename InputIt>
    [[nodiscard]] ::std::vector<Item> WorkOnIt(InputIt first, InputIt last)
    {
        ::std::cout << "explicit Item\n";
        ::std::vector<Item> result{first, last};
        //...fiddle with result...
        return result;
    }
    
    template <typename InputIt, typename Item = ::std::remove_const_t<typename InputIt::value_type>>
    [[nodiscard]] ::std::vector<Item> WorkOnIt(InputIt first, InputIt last)
    {
        ::std::cout << "implicit Item\n";
        return ::WorkOnIt<Item, InputIt>(first, last);
    }
    
    int main()
    {
        ::std::set<::std::string> goods{};
        ::std::vector<::std::string> cooked{};
        cooked = ::WorkOnIt<::std::string>(goods.begin(), goods.end());
        cooked = ::WorkOnIt(goods.begin(), goods.end());
        return 0;
    }
    

    online compiler

    explicit Item
    implicit Item
    explicit Item

    If forward declaration of this function is not required then code can be simplified to utilize deduced return type and to eliminate deduced Item parameter completely:

    #include <set>
    #include <string>
    #include <type_traits>
    #include <vector>
    #include <iostream>
    
    template <typename Item, typename InputIt>
    [[nodiscard]] auto WorkOnIt(InputIt first, InputIt last)
    {
        ::std::cout << "explicit Item\n";
        ::std::vector<Item> result{first, last};
        //...fiddle with result...
        return result;
    }
    
    template <typename InputIt>
    [[nodiscard]] auto WorkOnIt(InputIt first, InputIt last)
    {
        ::std::cout << "implicit Item\n";
        return ::WorkOnIt<::std::remove_const_t<typename InputIt::value_type>, InputIt>(first, last);
    }
    
    int main()
    {
        ::std::set<::std::string> goods{};
        ::std::vector<::std::string> cooked{};
        cooked = ::WorkOnIt<::std::string>(goods.begin(), goods.end());
        cooked = ::WorkOnIt(goods.begin(), goods.end());
        return 0;
    }
    

    online compiler