Search code examples
c++template-meta-programming

Template metaprogramming recursive evaluation


template<typename T>
struct rm_const_volatile {
    using type = T;
};

// Call this (partial)specialization as "1"
template<typename T>
struct rm_const_volatile<const T> {
    // remove topmost const and recurse on T
    using type = typename rm_const_volatile<T>::type;
};

// Call this (partial)specialization as "2"
template<typename T>
struct rm_const_volatile<volatile T> {
    // remove topmost volatile and recurse on T
    using type = typename rm_const_volatile<T>::type;  
};

I had defined the remove const volatile qualifier template meta-program as above.

Logically, when I write rm_const_volatile<const volatile int>, my expectation is that compiler will evaluate in the following order:

Case 1:

  • rm_const_volatile<const volatile int>
  • [Use specialization 1]-> rm_const_volatile<volatile int>
  • [Use specialization 2]-> rm_const_volatile<int>
  • [Use general template]-> int (final result).

Case 2:

  • rm_const_volatile<volatile const int>
  • [Use specialization 2]-> rm_const_volatile<const int>
  • [Use specialization 1]-> rm_const_volatile<int>
  • [Use general template]-> int (final result).

So in my view above template specialization and general definition suffices to remove any const volatile qualifiers.

Cut the actual behavior errors out as ambiguous template instantiation.

// In main() below two calls are present (refer cpp.sh link below mentioned for full code)

----
   std::cout << "Is volatile const int integral type with rm_const_volatile: " << is_integral<rm_const_volatile<volatile const int>::type>::value << std::endl;
   
   std::cout << "Is const volatile int integral type with rm_const_volatile: " << is_integral<rm_const_volatile<const volatile int>::type>::value << std::endl;

-------

 In function 'int main()':
54:132: error: ambiguous class template instantiation for 'struct rm_const_volatile<const volatile int>'
26:8: error: candidates are: struct rm_const_volatile<const T>
31:8: error:                 struct rm_const_volatile<volatile T>
54:95: error: incomplete type 'rm_const_volatile<const volatile int>' used in nested name specifier
54:95: error: incomplete type 'rm_const_volatile<const volatile int>' used in nested name specifier
54:138: error: template argument 1 is invalid

When I add following template specialization, everything works fine.

template<typename T>
struct rm_const_volatile<const volatile T> {
    using type = T;  
};

Please refer to this link for the full code.

I would like to know why the compiler reports template instantiation as ambiguous, when clearly it can chop off the top most qualifier and recursively instantiate to the final result as I have mentioned in the case 1 & 2 above.

Highly appreciate your valuable inputs and my sincere thanks for your time.


Solution

  • I would like to know why the compiler reports template instantiation as ambiguous

    C++ considers const volatile int and volatile const int to be the same type. They are interchangable and mean the same thing.

    Some other ways to spell this one type:

    • int const volatile
    • int volatile const
    • const int volatile
    • volatile int const

    That said, C++ will never change overload rules based on the way you choose to spell a particular type.

    Therefore, the template specializations <const T> and <volatile T> are both equally specialized for <const volatile int>. Neither qualifier takes priority over the other.