Search code examples
c++templatesforwarding-reference

C++ template function unused typename


I'm trying to get my head around C++ templates and copy semantics. I understand why I can't call assign1 (It asks for rvalue references and I'm providing an lvalue ref) But then why am I able to call assign2 which imposes the same constraints?

template<typename... Args>
void assign1(Args&& ... arguments) { }

template<typename A, typename... Args>
void assign2(Args&& ... arguments) { }

template<typename A, typename B, typename... Args>
void assign3(Args&& ... arguments) { }

int main() {
    const int r = 2;
    assign1<int>(r);        // no matching function for call to...
    assign2<int>(r);        // ok!
    assign3<int>(r);        // no matching function for call to...
}


Solution

  • Given assign1<int>(r);, you're specifying the template argument explicitly, then the parameter type of assign1 would be int&&, as you said, it's rvalue-reference and can't be bound with lvalue.

    Given assign2<int>(r);, you're specifying the 1st template argument A as int, the parameter pack Args will be deduced from the function argument r. Note that it's not rvalue-reference but forwarding reference, which could accpet both lvalues and rvalues. (Depending on the function arguments are lvalues or rvalues, according to the type deduction result, the function parameter type would be lvalue-reference or rvalue-reference.)

    Given assign3<int>(r);, you're specifying only the 1st template argument A, but the 2nd parameter parameter B can't be deduced from function argument and the calling fails.


    If you want to make the function template accepting rvalues only, you can add static_assert like

    template<typename A, typename... Args>
    void assign2(Args&& ...) { 
        static_assert(((!std::is_lvalue_reference_v<Args>) && ...), "must be rvalue");
    }
    

    LIVE

    Or apply SFINAE.

    template<typename A, typename... Args>
    std::enable_if_t<((!std::is_lvalue_reference_v<Args>) && ...)> assign2(Args&& ...) {
    }
    

    LIVE

    Or add another overload taking lvalue-references and mark it as delete. (This approach works only when all the arguments are lvalues.)

    template<typename A, typename... Args> 
    void assign2(Args& ... arguments) = delete;
    

    LIVE