Hatatister
Hatatister

Reputation: 1052

Problems with function template and type_traits

I have a function "has_holes", which shall calculate something based on a mask. The number of bits is determined by the type of "mask". Therefore I want to use a template. Furthermore, I only want to allow instantiations of has_holes which take parameters by value. So I added a typetrait "remove_all_t" which gives the underlying type. But when I do so, I cant build it any more getting the error:

"error: no matching function for call to 'has_holes(unsigned int&)" note: candidate template ignored: couldn't infer template argument 'BASE_TYPE'

However, if I explicitly call the funktion instantiation "has_holes it works. Did I mess up template instantiation and type deduction rules? Or where is my mistake?

Here is the code:

#include <iostream>
#include <limits>
#include <type_traits>
#include <experimental/type_traits>


//removes everything except arrays []
template<class T> struct remove_all { typedef T type; };
template<class T> struct remove_all<T*> : remove_all<T> {};
template<class T> struct remove_all<T&> : remove_all<T> {};
template<class T> struct remove_all<T&&> : remove_all<T> {};
template<class T> struct remove_all<T const> : remove_all<T> {};
template<class T> struct remove_all<T volatile> : remove_all<T> {};
template<class T> struct remove_all<T const volatile> : remove_all<T> {};
template<class T>
using remove_all_t = typename remove_all<T>::type;

template<typename BASE_TYPE, class = typename std::enable_if_t<std::experimental::is_unsigned_v<BASE_TYPE>, BASE_TYPE>>
bool has_holes(remove_all_t<BASE_TYPE> mask){ //remove "remove_all_t<>" and it will work

    static_assert(std::numeric_limits<unsigned int>::max() > std::numeric_limits<decltype(mask) >::digits, "Base_type has to much digits max_digits=std::numeric_limits<unsigned int>::max()");
    for (unsigned int  pos{1}; pos<std::numeric_limits<decltype(mask)>::digits; ++pos ){
        ;//algorithm will be placed here, not implemented yet
    }
    return true;
}

int main()
{
    unsigned int mask = 0b00110011;
    auto result = has_holes<unsigned int>(mask); //works
    auto result2 = has_holes(mask);//error: no matching function for call to 'has_holes(unsigned int&)'|
    std::cout<<result<<" ..."<<result2<<std::endl;
    return 0;
}

Best regards, Hendrik

Upvotes: 1

Views: 503

Answers (3)

Marco A.
Marco A.

Reputation: 43662

Template argument deduction is attempted, as described in [temp.deduct.call]/p1 with P = typename remove_all<BASE_TYPE>::type and A = unsigned int. Eventually BASE_TYPE cannot be deduced (nor defaulted) and that turns into an incomplete deduction per [temp.deduct.type]/p2.

As a sidenote, I would use a simple workaround (since you already have other static assertions in place)

template<typename BASE_TYPE, class = typename std::enable_if_t<std::experimental::is_unsigned_v<BASE_TYPE>, BASE_TYPE>, 
         typename RBASE_TYPE = remove_all_t<BASE_TYPE>>
                  ^^^^^^^^^^
bool has_holes(BASE_TYPE mask){

    // Use RBASE_TYPE here

    return true;
}

Simplified example

Upvotes: 0

Walter
Walter

Reputation: 45474

Since you're concerned about the number of bits, just do the type reductions when you work out the number of bits.

template<typename BASE_TYPE,
         class = typename std::enable_if_t<std::experimental::is_unsigned_v<remove_all_t<BASE_TYPE> > > 
        >
bool has_holes(BASE_TYPE mask)
{
    using reduced_type = remove_all_t<BASE_TYPE>;
    static_assert(std::numeric_limits<unsigned int>::max() > std::numeric_limits<reduced_type>::digits,
                  "Base_type has to many digits max_digits=std::numeric_limits<unsigned int>::max()");
    for (unsigned int pos{1};    // really start at 1 (nor 0)?
         pos < std::numeric_limits<reduced_type>::digits;
         ++pos )
    {
        //algorithm will be placed here, not implemented yet
    }
    return true;
}

though you presumably don't want to accept BASE_TYPE* (so must modify this).

Upvotes: 0

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275896

In C++ template deduction is not unlimited. It will refuse to attempt to invert possibly Turing-complete processes.

The C++ standard calls this a non-deduced context: a context in which template arguments will not be deduced.

remove_all_t<BASE_TYPE> induces a non-deduced context. Because when you map a type through a template like remove_all_t<X> in theory the mapping process could be Turing complete.

And in general, template type maps do not tell C++ how to invert them. The standard tells compilers not to try.

Remove remove_all_t<BASE_TYPE> and replace with BASE_TYPE and it will be deduced as a value, always.

If you are afraid someone will pass int& explicitly as a template type parameter, add a static_assert( std::is_same<remove_all_t<BASE_TYPE>, BASE_TYPE>::value, "values only"); to the body of the function.

Upvotes: 1

Related Questions