Search code examples
c++c++11template-meta-programmingtype-deduction

How to filter const types and non const types using meta programing?


I have this code

#include <iostream>

size_t F()
{
        return 0;
}

template <class Type, class... NextTypes>
size_t F(const Type& type, const NextTypes&... nextTypes)
{
        if (!std::is_const<Type>::value)
                return sizeof(type) + F(nextTypes...);
        else
                return F(nextTypes...);
}

int main()
{
  int a = 0;
  const int b = 0;
  const size_t size = F(a,b);
  std::cout << "size = " << size << std::endl;
  return 0;
}

I'm trying to know in compilation time the total size of constant parameters and non const parameters. The current out put is 8, for some reason the compiler thinks b is not constant, I used typeid and decltype to print the types of a and b and indeed the output shows b is an int and not const int as I expected. What am I missing? Is it possible to separate a variadic set of arguments to const arguments and non const?


Solution

  • Consider this function template:

    template<typename T>
    void deduce(const T&);
    

    If you let the compiler deduce a type for T from an argument expression, the deduced type will never be const: It will try to make the const T of the function parameter identical to the type of the argument expression used to call the function. For example:

    struct cls {};
    const cls c;
    
    deduce(c) // deduces T == cls
    

    By deducing T == cls, the compiler succeeds in making const T identical to the argument type const cls. There is no reason for the compiler to produce two different functions for const- and non-const argument types; the parameter type of the function template instantiation will be const-qualified in any case: you requested it by saying const T& instead of, say, T&.


    You can deduce the const-ness of an argument by not cv-qualifying the function parameter:

    template<typename T>
    void deduce(T&);
    

    However, this will fail to bind to non-const temporaries (rvalues). To support them as well, you can use universal references:

    template<typename T>
    void deduce(T&&);
    

    This will deduce an lvalue-reference type for T if the argument is an lvalue, and no reference if the argument is an rvalue. The const-ness will be deduced correctly.

    For example, if the argument has the type const A and is an lvalue, T will be deduced to const A&. The function parameter then is const A& &&, which is collapsed to const A& (an lvalue-reference). If the argument is an rvalue, T will be deduced to const A, and the function parameter becomes const A&& (an rvalue-reference).

    Note that since T can be a reference in this case, you need to remove that before checking for const-ness: std::is_const< typename std::remove_reference<T>::type >::value.