gcc4 template bug or more likely id10t error
Asked Answered
U

3

5

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]
Ufo answered 27/1, 2012 at 18:6 Comment(4)
+1 for a complete minimal example programChen
@Mr.Anubis : The standard mandates how overload resolution works, so it shouldn't be compiler dependent.Natation
@Natation I got that wrong, Thanks :)Zebu
@Zebu : I'm not sure -- you say "If you had second argument of type pointer to typename AddPkgrConst<Packager, bool>::type then overload could have selected the second one" but the problem is that in this case typename AddPkgrConst<Packager, bool>::type resolves to bool, which is what's being passed in, so I'm confused as to what you're getting at.Natation
S
4

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.

Symbolize answered 27/1, 2012 at 21:30 Comment(13)
It's VC++ that's in error, not GCC. His code is resolving to Package(ReadPackager*, bool*), which is very much ambiguous.Natation
Template ambiguities are supposed to be resolved using template partial ordering. The standard describes the quite complicated procedure involved in determining whether one template is more specialized than another. For instance, "template<typename Packager> Package(Packager*, bool*)" would also take the same arguments as the existing functions, but gcc and other compilers correctly determine that it is more specialized then the first template and do not emit an error.Symbolize
Indeed it does, but that's irrelevant. Package(Packager*, typename AddPkgrConst<Packager, bool>::type*) is not magically more specialized than Package(Packager*, T*) just because it makes use of a metafunction; in this context, both AddPkgrConst<Packager, bool>::type and T resolve to bool. Package(Packager*, bool*), on the other hand, is more specialized. (To be clear, the reason it's irrelevant is because Packager in AddPkgrConst<Packager, bool>::type is a dependent type.)Natation
I explained in the original comment why I thought the second Package function was more specialized than the first. There was no magic, and it had nothing to do with dependent types. If you can explain what part of the partial ordering described in the standard makes the second function no more specialized than the first, that would be helpful.Symbolize
"I explained in the original comment why I thought the second Package function was more specialized than the first." No you didn't, you just keep mentioning template partial ordering. C++11 §14.5.6.2/3: "To produce the transformed template, for each type, non-type, or template template parameter (including template parameter packs (14.5.3) thereof) synthesize a unique type, value, or class template respectively and substitute it for each occurrence of that parameter in the function type of the template." (cont'd)Natation
(cont'd) So after substitutions Package(Packager*, T*) becomes Package(ReadPackager*, bool*) and Package(Packager*, typename AddPkgrConst<Packager, bool>::type*) becomes Package(ReadPackager*, bool*). Then template argument deduction is performed (§14.8.2.4). I admit this part of the standard is terse and lacking clear examples, so I may very well be missing something, but the burden of proof is on you. ;-] As far as I can tell, if the second argument of the second overload was AddPkgrConst<Packager, bool> rather than AddPkgrConst<Packager, bool>::type then you would be correct.Natation
You quoted the standard, but failed to follow the part you quoted. The compiler is supposed to "synthesize a unique type" for each template parameter. The key thing to remember here is that the types used for instantiation have absolutely nothing to do with partial ordering of templates. Example: synthesize types U1, U2 for the first template function to get Package(U1*, U2*). Can those parameters be used to call Package(Packager*, typename AddPkgrConst<Packager,bool>::type*)? After deduction, this is Package(U1*, AddPkgrConst<U1,bool>::type*) or Package(U1*, bool*). U2 is not bool, so no.Symbolize
Apologies, I'm not at home yet and a bit rushed; agreed that I didn't follow it correctly, but I quoted the standard partly so you could follow it. Again, the burden of proof is on you to demonstrate why you're right; Comeau, Clang tip-of-trunk, and GCC 4.3-4.6 all reject this simplified version: ideone.com/Au8IP Why are they all wrong and you're right?Natation
Your suggestion that I did not explain why I thought the second function was more specialized than the first and only went on about partial ordering is inaccurate. It is in the second and third paragraphs of the "answer" I originally provided. (I referred to this as my original comment, which may have caused confusion.)Symbolize
Sorry, I'm not convinced you're correct and that Comeau et al are incorrect until you provide the actual standardese that backs up your statement. Note that I've not downvoted your answer because the potential that you're correct is still there, it will just take a lot of convincing that two extremely conformant compilers are getting this simple thing wrong. :-] (And of course, I'd love to understand the details more clearly, and since you seem to be well-versed in them, so you're in a good position to educate everyone.)Natation
Using the simplified explanation of the standard from here: A template X is more specialized than a template Y if every argument list that matches the one specified by X also matches the one specified by Y, but not the other way around., it seems obvious to me that the code should compile. I even tried following the "unique type" substitution from the standard and came to the same conclusion. If the compilers are right, I sure can't figure out why.Symbolize
I edited the answer to address with more detail specifically how the standard applies.Symbolize
Thanks for the update, and +1 for making a proper answer. :-] I'm going to try to wrap my head around this over the weekend. Oh, and welcome to SO. ;-]Natation
S
3

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.

Spermatogonium answered 27/1, 2012 at 18:22 Comment(4)
AddPkgrConst<Packager,bool>::type does not use any specialization in this context because there is no specialization for <CReadType, bool> (only <CReadType, bool const>). So, the general (non-specialized) definition is used instead, which also simply does typedef T type.Natation
@ildjarn: Thanks, I actually meant the first implementation, but wrote specialisation instead. I'll fix it.Spermatogonium
Actually what gets used is a partial specialisation.Korey
@R.MartinhoFernandes : Ah, you're right; I missed that due to poor formatting (which Mooing Duck fixed).Natation
N
1

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.
}
Nictitate answered 27/1, 2012 at 18:59 Comment(1)
AddPkgrConst is a struct with partial specialization that performs the wrapping in this case. The const specialization should be enough to generate a unique type even if it strips the const in the typedef T type within. This should still be enough to resolve the function overload without ambiguity.Conversational

© 2022 - 2024 — McMap. All rights reserved.