Search code examples
c++templatestemplate-argument-deductiontype-deduction

Force non-deduced context - type_identity etc


I'm trying to make a template which deduces a type associated with one template parameter, while treating other types in a non-deduced context. A minimal example is provided below. I can do what I'm looking for using two deduction contexts, but can it be done in one? I thought I'd try using something like type_identity, but I'm not having any luck.

Live example: https://onlinegdb.com/By0iDOM-P

template<size_t... Idx, size_t s>
void foo(index_sequence<Idx..., s>) {
    cout << s << endl;
}

template<typename T>
struct TypeIdentity {
    using Type = T;
};

template<typename T>
using Identity = typename TypeIdentity<T>::Type;

template<typename... Idx, size_t s>
void bar(index_sequence<Identity<Idx>{}..., s>) {
    cout << s << endl;
}

template<size_t s>
void baz(index_sequence<0, s>) {
    cout << s << endl;
}

template<size_t... Idx>
struct Qux {
    template<size_t s>
    static void qux(index_sequence<Idx..., s>) {
        cout << s << endl;
    }
};

int main()
{
    foo<0>(make_index_sequence<2>{}); // couldn't deduce template parameter ‘s’
    bar<integral_constant<size_t, 0>>(make_index_sequence<2>{}); // couldn't deduce template parameter ‘s’
    baz(make_index_sequence<2>{});
    Qux<0>::template qux(make_index_sequence<2>{});

    return 0;
}

Solution

  • No. The problem is that Idx... pack is too greedy to leave anything for s. You can use s, Idx... to get the first value, but you can't use Idx..., s to get the last one. TypeIdentity won't help you to do that and you need some other tricks.

    For example, unpacking into an array (C++14):

    template<std::size_t first, std::size_t... rest>
    void foo(std::index_sequence<first, rest...>) {
        const std::size_t indices[]{first, rest...};
        const std::size_t last = indices[sizeof...(rest)];
    
        std::cout << last << std::endl;
    }
    

    or using a fold expression with a comma operator (C++17):

    template<std::size_t first, std::size_t... rest>
    void foo(index_sequence<first, rest...>) {
        const std::size_t last = (first, ..., rest);
        std::cout << last << std::endl;
    }
    

    I isolated first to make sure that the whole pack first, rest... is never empty.