Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

gcc4 template bug or more likely id10t error

The following code compiles just fine under Visual Studio but neither gcc 4.6.2 or 4.7 can handle it. It seems to be valid but gcc can't seem to resolve the difference between const and non const parameters. Could this be a compiler bug?

struct CReadType{};
struct CWriteType{};

template<typename ReadWriteType, typename T> 
struct AddPkgrConstByType {}; 
template<typename T> 
struct AddPkgrConstByType<CReadType, T> {
   typedef T type;
};    
template<typename T>
struct AddPkgrConstByType<CReadType, const T> {
    typedef T type;
};
template<typename T>
struct AddPkgrConstByType<CWriteType, T> {
    typedef T const type;
};

template<typename Packager, typename T>
struct AddPkgrConst : public AddPkgrConstByType<typename Packager::CReadWriteType, T> {
};

template<typename Packager, typename T>
inline bool Package( Packager* ppkgr, T* pt ) 
{
    return true;
}

template<typename Packager>
inline bool Package( Packager* ppkgr, typename AddPkgrConst<Packager,bool>::type* pb) 
{
    return false;
}

struct ReadPackager {
    typedef CReadType CReadWriteType;
};
struct WritePackager {
    typedef CWriteType CReadWriteType;
};

int main(int argc, char* argv[])
{
    ReadPackager rp;
    WritePackager wp;
    bool b = true;
    const bool cb = false;
    Package( &rp, &b );
}

Compiler call:

g++ -fPIC -O -std=c++0x -Wno-deprecated -D_REENTRANT 
g++-D__STDC_LIMIT_MACROS -c test.cpp
test.cpp: In function ‘int main(int, char**)’:
test.cpp:58:22: error: call of overloaded ‘Package(ReadPackager*, bool*)’ is ambiguous
test.cpp:58:22: note: candidates are:
test.cpp:31:6: note: bool Package(Packager*, T*) [with Packager = ReadPackager, T = bool]
test.cpp:38:6: note: bool Package(Packager*, typename AddPkgrConst<Packager, bool>::type*) [with Packager = ReadPackager, typename AddPkgrConst<Packager, bool>::type = bool]
like image 968
user1174067 Avatar asked Jan 27 '12 18:01

user1174067


3 Answers

This looks like a compiler error to me. The issues involved here are overload resolution and partial ordering of template functions. Since both template functions can match the argument list (ReadPackager*, bool), partial ordering of template functions should be used to choose the more specialized template function.

Put simply, a template function is at least as specialized as another if the arguments to that function can always be used as arguments to the other.

It's clear that any two pointer arguments match the first Package() function, but for instance Package(ReadPackager*, const int*) can not match the second. This seems to imply that the second Package function is more specialized and ought to resolve any ambiguity.

However, since there is disagreement among the compilers, there may be some subtleties involved that are overlooked by the simplified explanation. I will therefore follow the procedure for determining function template partial ordering from the standard to discern the correct behavior.

First, labeling the functions as P1 and P2 for easy reference.

P1:

template<typename Packager, typename T>
bool Package( Packager* ppkgr, T* pt );

P2:

template<typename Packager>
bool Package( Packager* ppkgr, typename AddPkgrConst<Packager,bool>::type* pb);

The standard says that for each template function (T1), we must generic unique type for each of its template parameters, use those types to determine the function call parameter types, and then use those types to deduce the types in the other template (T2). If this succeeds, the first template (T1) is at least as specialized as the second (T2). First P2->P1

  1. Synthesize unique type U for template parameter Packager of P2.
  2. Perform type deduction against P1's parameter list. Packager is deduced to be U and T is deduced to be AddPkgrConst<Packager,U>::type.

This succeeds and P1 is judged to be no more specialized than P2.

Now P1->P2:

  1. Synthesize unique types U1 and U2 for template parameters Packager and T of P1 to get the parameter list (U1*, U2*).
  2. Perform type deduction against P2's parameter list. Packager is deduced to be U1.
  3. No deduction is performed for the second parameter because, being a dependent type, it is considered a non-deduced context.
  4. The second argument is therefore AddPkgrConst<U1,bool>::type which evaluates to bool. This does not match the second parameter U2.

This procedure fails if we proceed to step 4. However, my suspicion is that the compilers that reject this code don't perform step 4 and therefore consider P2 no more specialized than P1 merely because type deduction succeeded. This seems counter intuitive since P1 clearly accepts any input that P2 does and not vice versa. This part of the standard is somewhat convoluted, so it's not clear whether this final comparison is required to be made.

Let's try to address this question by applying §14.8.2.5, paragraph 1, Deducing template arguments from a type

Template arguments can be deduced in several different contexts, but in each case a type that is specified in terms of template parameters (call it P) is compared with an actual type (call it A), and an attempt is made to find template argument values (a type for a type parameter, a value for a non-type parameter, or a template for a template parameter) that will make P, after substitution of the deduced values (call it the deduced A), compatible with A.

In our type deduction, the deduced A is AddPkgrConst<U1,bool>::type=bool. This is not compatible with the original A, which is the unique type U2. This seems to support the position that the partial ordering resolves the ambiguity.

like image 103
Mark Avatar answered Oct 15 '22 03:10

Mark


I don't know what's wrong with Visual Studio, but what gcc says seems right:

You instantiate AddPkgrConstByType<CReadType, T> because Packager::CReadWriteType resolves to CReadType. Therefore, AddPkgrConst<Packager,bool>::type will resolve according to the first implementation (which is not a specialisation) to bool. This means you have two separate function specialisations with the same parameter list, which C++ doesn't allow you.

like image 45
bitmask Avatar answered Oct 15 '22 04:10

bitmask


Since function templates can't be specialized, what you have here is two function template overloads. Both of these overloads are capable of accepting a bool* as their second argument so they appear to properly be detected as ambiguous by g++.

However classes can be partially specialized so that only one version will be picked, and you can use wrapper magic to attain your desired goal. I'm only pasting the code I added.

template <typename Packager, typename T>
struct Wrapper
{
    static bool Package()
    {
        return true;
    }
};

template <typename Packager>
struct Wrapper<Packager, typename AddPkgrConst<Packager,bool>::type>
{
    static bool Package()
    {
        return false;
    }
};

template <typename Packager, typename T>
Wrapper<Packager, T> make_wrapper(Packager* /*p*/, T* /*t*/)
{
    return Wrapper<Packager, T>();
}

int main()
{
    ReadPackager rp;
    bool b = true;
    std::cout << make_wrapper(&rp, &b).Package() << std::endl;  // Prints out 0.
}
like image 1
Mark B Avatar answered Oct 15 '22 05:10

Mark B