Writing generic code in MQL for Objects [solved] - page 4

 
Dominik Christian Egert #:

I would like to hear your opinion on this code:


As you can see, I did a cast on the constant variable, so it is required to match the third template. - But, if I dont cast the variable, the compiler cannot select a matching template, and complains about ambiguous call. - But shouldnt this also be the case for the non-const call one line above??

Why does the compiler select the non-reference call for the non-const variable, instead of the reference-call. - And, why does the const variable not match as the non-const variable.


This is (I am quite confident now) obviously an inconsistent behaviour, especially, because it works for pointer, objcets and structs exactly as expected. - I make my claim, this is a bug in the compilers template matching algorithm.

What do you think?


EDIT:

And to proove my point further, if you change the first template like this, and change the call to the __func() as shown below, the compiler suddenly switches over to have issues with the non-const variable. The behaviour now is vice-versa between "int" and "const int".



If you remove the cast operation within the function call, the compiler will complain again. - And it is vice-versa with the const declarations. - The compilers matching for "const" and "non-const" is mixed up.

Well I think you will not like my conclusions

Short version :

    __func(target_int, 7);
    __func(target_int, _int);
    __func(target_int, (int)_int);
    __func(target_int, c_int);                
    __func(target_int, (const int)c_int);    

If we take all these variants, with const templates none of them should compile and all should be ambiguous.

At least the last 4. As in MQL a literal can't be passed to a const by reference (which should be possible like in it's in C++, that would be useful).

So I do agree with you there is an inconsistent behaviour, but not where you think it is, the consistent behaviour would be to reject all. It's by design of MQ developers.

Longer version :

The origin of the issue is the usage of a template by value and one by reference with the same types. How would you choose between the by value and the by ref version ? You can try it in C++, it doesn't work either.

With non-const template, it's a bit different. Like in c++ the explicit cast will work to disambiguated the template to use, because (const int) or (int) casts will call the by value version. You can't use (const int &) and (int &) casts to pick the "by reference" version because again it leads to ambiguity (and anyway this is not valid in MQL). So only 2 of the 5 call are ambiguous.

Here is the C++ code :

#include <iostream>

using namespace std;

//--- Template 1
template <typename TARGET_TYPE, typename SOURCE_TYPE>
void __func(TARGET_TYPE& p_out, const SOURCE_TYPE& p_in)
  {
   p_out = p_in;
   std::cout << "Template 1!";
  };
//--- Template 3
template <typename TARGET_TYPE, typename SOURCE_TYPE>
void __func(TARGET_TYPE& p_out, const SOURCE_TYPE p_in)
  {
   p_out = p_in;
   std::cout << "Template 3!";
};

int main()
{
   int             target_int;
   int             _int        = 5;
   const int    c_int       = 8;
 

   __func(target_int, 7);
   __func(target_int, _int);
   __func(target_int, (int)_int);
   __func(target_int, c_int);
   __func(target_int, (const int)c_int);

    return 0;
}

Which gives :

(Again the site removed all <CR><LF> from the log I posted, so I decided to use a picture).


The source type is always int. Which is perfectly understandable as the const is explicit in the template. You can read about "Template argument deduction in C++".

So MQL, is actually less restrictive than C++, it allows you to use an explicit cast and pick the by value in priority without a cast. These are design choices in my opinion, not bugs. If you want them to "fix" or change something, it will lead to more issues with what you want to do.

As a last conclusion MQL is not C++, the developers make some design choices, these are not bugs (well it would need to confirm there are real choice and not done "by chance"). I am not sure asking them to change those choices would work neither if it's desirable.

Example of design choice :

This is (I am quite confident now) obviously an inconsistent behaviour, especially, because it works for pointer, objects and structs exactly as expected.

Yes because there is no ambiguity with pointers (template 2 is used), and because objects and structs can NOT be passed by value, so template 3 is irrelevant.





 
Dominik Christian Egert #:

I continued to work on a solution, and here it is:

Compiles and works as expected.... - Its demo-code and its functionality is not the point here, its about the type and type casting as well as the possibility to formulate "universal" code for having one function name called with any given datatype, which this code has achieved.

Still, this is not the way the keyword "const" should work. - I am sure, this is an implementation error.

Maybe someone could give some insigt on how this could be explained, or any other point of view, because, maybe my interpretation is wrong.

This is coherent with my previous post and analysis. We can deduce some additional design choices made for MQL :

* If there are 2 "by reference" template versions, the non-const version will be used.

This line :

   test_int_tpl.__func(target_int, _int);    // Is using this version    void              __func(USER_DATA& p_out, USER_DATA& p_in)

* The non-template version(s) has priority over template version(s). (The above is not ambiguous with template versions).

* If there 2 matching versions, one using const-correctness, the other one will be used.

This line :

   test_int_tpl.__call(c_int);   

// Will use 1. If you remove the highlighted const, it lead to an ambiguity.
   template <typename CALL_TYPE>
   1. void              __call(const CALL_TYPE& p_in)
   template <typename CALL_TYPE>
   2. void              __call(const CALL_TYPE p_in) const

EDIT:

There is something incoherent or which I missed the logic behind it.

In the previous version of Dominik code :

// This line 
__func(target_int, _int);
// is using this version of the template 
template <typename TARGET_TYPE, typename SOURCE_TYPE>
void __func(TARGET_TYPE& p_out, const SOURCE_TYPE p_in)
// while the other choice is 
template <typename TARGET_TYPE, typename SOURCE_TYPE>
void__func(TARGET_TYPE& p_out, const SOURCE_TYPE& p_in)

While in last code version :

// This line :
   test_int_tpl.__call(_int);
// Will use this template version
   template <typename CALL_TYPE>
   void              __call(const CALL_TYPE& p_in)
// The other choice being (I removed the trailing "const"
   template <typename CALL_TYPE>
   void              __call(const CALL_TYPE p_in) const

So the logic seems reversed and didn't lead to an ambiguity. The only difference is the first code is non member template functions, while the last is class member template functions.

That could be a bug.

 
Alain Verleyen #:

This is coherent with my previous post and analysis. We can deduce some additional design choices made for MQL :

* If there are 2 "by reference" template versions, the non-const version will be used.

This line :

* The non-template version(s) has priority over template version(s). (The above is not ambiguous with template versions).

* If there 2 matching versions, one using const-correctness, the other one will be used.

This line :

EDIT:

There is something incoherent or which I missed the logic behind it.

In the previous version of Dominik code :

While in last code version :

So the logic seems reversed and didn't lead to an ambiguity. The only difference is the first code is non member template functions, while the last is class member template functions.

That could be a bug.

I see... - You really took your time to dig into this, thank you for the effort and obviously you fond something, I didnt see.

Aside from C/C++, lets try to stick with MQL5 compiler for this, although its a good reference.

I tried to point out following, and maybe I am mistaking this for an error. I somehow seem to not get my head around it.


Following code does not compile, but the errors seem to lead me to where the twist (I am presuming) is having its effect. - I assume its due to the fact, const gets dropped, but is it correct to drop it in this regard, as classes, pointers and structs do not drop it when expanding a template. -

I have removed all const keywords just to be sure, we are not mixing in anything.

template <typename USER_DATA>
struct _test
{
    USER_DATA __dummy;


    template <typename UNIFIED>
    const bool __sub(const UNIFIED* p_in)
    { return((p_in != NULL) ? (__dummy > p_in) : true); };
    
    template <typename UNIFIED>
    const bool __sub(const UNIFIED& p_in)
    { return(__dummy > p_in); };



//    void __func(USER_DATA& p_out, USER_DATA& p_in)
//    { __sub(p_in); };
    
    void __func(USER_DATA& p_out, USER_DATA& p_in)
    { __sub(p_in); };
    

    template <typename TARGET_TYPE, typename SOURCE_TYPE>
    void __func(TARGET_TYPE*& p_out, SOURCE_TYPE* p_in)
    { __sub(dynamic_cast<const TARGET_TYPE*>(p_in)); };
    
    template <typename TARGET_TYPE, typename SOURCE_TYPE>
    void __func(TARGET_TYPE& p_out, SOURCE_TYPE* p_in)
    { __sub(dynamic_cast<const TARGET_TYPE*>(p_in)); };
    
    template <typename TARGET_TYPE, typename SOURCE_TYPE>
    void __func(TARGET_TYPE*& p_out, SOURCE_TYPE& p_in)
    { __sub(dynamic_cast<const TARGET_TYPE*>(&p_in)); };
    
    template <typename TARGET_TYPE, typename SOURCE_TYPE>
    void __func(TARGET_TYPE& p_out, SOURCE_TYPE& p_in)
    { __sub(TARGET_TYPE(p_in)); };
    
    template <typename TARGET_TYPE, typename SOURCE_TYPE>
    void __func(TARGET_TYPE& p_out, SOURCE_TYPE p_in)
    { __sub(p_in); };


    template <typename CALL_TYPE>
    void __call(CALL_TYPE* p_in)
    { __func(__dummy, p_in); };

    template <typename CALL_TYPE>
    void __call(CALL_TYPE& p_in)
    { __func(__dummy, p_in); };

    template <typename CALL_TYPE>
    void __call(CALL_TYPE p_in)
    { __func(__dummy, p_in); };

};




void OnStart()
{
        int                         _int        = 5;
        const int                   c_int       = 8;

    _test<int> test_int_tpl;

    test_int_tpl.__call(7);           
    test_int_tpl.__call((int)7);      
    test_int_tpl.__call((const int)7);      
    test_int_tpl.__call(_int);        
    test_int_tpl.__call((int)_int);   
    test_int_tpl.__call((const int)_int); 
    test_int_tpl.__call(c_int);           
    test_int_tpl.__call((int)c_int);      
    test_int_tpl.__call((const int)c_int);
}


And in comparison this code:

template <typename USER_DATA>
struct _test
{
    USER_DATA __dummy;


    template <typename UNIFIED>
    const bool __sub(const UNIFIED* p_in)
    { return((p_in != NULL) ? (__dummy > p_in) : true); };
    
    template <typename UNIFIED>
    const bool __sub(const UNIFIED& p_in)
    { return(__dummy > p_in); };



//    void __func(USER_DATA& p_out, USER_DATA& p_in)
//    { __sub(p_in); };
    
    void __func(USER_DATA& p_out, USER_DATA& p_in)
    { __sub(p_in); };
    

    template <typename TARGET_TYPE, typename SOURCE_TYPE>
    void __func(TARGET_TYPE*& p_out, SOURCE_TYPE* p_in)
    { __sub(dynamic_cast<const TARGET_TYPE*>(p_in)); };
    
    template <typename TARGET_TYPE, typename SOURCE_TYPE>
    void __func(TARGET_TYPE& p_out, SOURCE_TYPE* p_in)
    { __sub(dynamic_cast<const TARGET_TYPE*>(p_in)); };
    
    template <typename TARGET_TYPE, typename SOURCE_TYPE>
    void __func(TARGET_TYPE*& p_out, SOURCE_TYPE& p_in)
    { __sub(dynamic_cast<const TARGET_TYPE*>(&p_in)); };
    
    template <typename TARGET_TYPE, typename SOURCE_TYPE>
    void __func(TARGET_TYPE& p_out, SOURCE_TYPE& p_in)
    { __sub(TARGET_TYPE(p_in)); };
    
    template <typename TARGET_TYPE, typename SOURCE_TYPE>
    void __func(TARGET_TYPE& p_out, SOURCE_TYPE p_in)
    { __sub(p_in); };


    template <typename CALL_TYPE>
    void __call(CALL_TYPE* p_in)
    { __func(__dummy, p_in); };

    template <typename CALL_TYPE>
    void __call(const CALL_TYPE& p_in)
    { __func(__dummy, p_in); };

    template <typename CALL_TYPE>
    void __call(CALL_TYPE p_in)
    { __func(__dummy, p_in); };

};




void OnStart()
{
        int                         _int        = 5;
        const int                   c_int       = 8;

    _test<int> test_int_tpl;

    test_int_tpl.__call(7);           
    test_int_tpl.__call((int)7);      
    test_int_tpl.__call((const int)7);      
    test_int_tpl.__call(_int);        
    test_int_tpl.__call((int)_int);   
    test_int_tpl.__call((const int)_int); 
    test_int_tpl.__call(c_int);           
    test_int_tpl.__call((int)c_int);      
    test_int_tpl.__call((const int)c_int);
}


Please note the difference in the function signatures.

First version;

    template <typename CALL_TYPE>
    void __call(CALL_TYPE& p_in)

will give compile error

referring to this function call:

    test_int_tpl.__call(_int);        


And when I change the function signature to this:

    template <typename CALL_TYPE>
    void __call(const CALL_TYPE& p_in)

I get following error:


referring to this call:

    test_int_tpl.__call(c_int);           


I dont think this is intended behaviour, or what the hell am I missing here???

At least, shouldnt it be exactly the other way around?


But here comes the twist:

With these signatures:

    template <typename CALL_TYPE>
    void __call(CALL_TYPE* p_in)
    { __func(__dummy, p_in); };

    template <typename CALL_TYPE>
    void __call(const CALL_TYPE& p_in)
    { __func(__dummy, p_in); };

    template <typename CALL_TYPE>
    void __call(CALL_TYPE p_in)
    { __func(__dummy, p_in); };


This function call:

    test_int_tpl.__call(_int);        


will match to this function:

    template <typename CALL_TYPE>
    void __call(CALL_TYPE p_in)
    { __func(__dummy, p_in); };



While when I use these signatures:

    template <typename CALL_TYPE>
    void __call(CALL_TYPE* p_in)
    { __func(__dummy, p_in); };

    template <typename CALL_TYPE>
    void __call(CALL_TYPE& p_in)
    { __func(__dummy, p_in); };

    template <typename CALL_TYPE>
    void __call(CALL_TYPE p_in)
    { __func(__dummy, p_in); };


This function call:

    test_int_tpl.__call(c_int);           

will match this function:

    template <typename CALL_TYPE>
    void __call(CALL_TYPE p_in)
    { __func(__dummy, p_in); };


In conclusion, this means, if I understand this right, in the first case, the const reference declared signature does not match. - becuase it is const. and we are passing in a non-cosnt variable, so the non-const signature is preferred.

In the second case, where the function signature has no const directives, and when passing in a const argument, the const directive is not being dropped while expanding the template, thus both templates, (CALL_TYPE x) and (const CALL_TYPE& x) would match. because (CALL_TYPE x) is expanded to (const CALL_TYPE x)


In the other case, with the signatures being (CALL_TYPE x) and (CALL_TYPE& x), both without const directive, when expanding with a non-const variable would expand to (int x) and (int& x).

But shouldnt they also expand to (const CALL_TYPE x) and (const CALLTYPE& x) for the const variable to be this: (const int x) and (const int& x) ... - Shouldnt this be as well ambiguous?

And if so, then shouldnt it be possible to declare a template with and without const, providing a match for both cases like this:

    template <typename CALL_TYPE>
    void __call(CALL_TYPE p_in)
    { __func(__dummy, p_in); };

    template <typename CALL_TYPE>
    void __call(const CALL_TYPE p_in)
    { __func(__dummy, p_in); };

Well, this does not work for following reason:



But why can I create a matching template like this:

    template <typename CALL_TYPE>
    void __call(CALL_TYPE* p_in)
    { __func(__dummy, p_in); };

    template <typename CALL_TYPE>
    void __call(CALL_TYPE& p_in)
    { __func(__dummy, p_in); };

    template <typename CALL_TYPE>
    void __call(const CALL_TYPE& p_in)
    { __func(__dummy, p_in); };

    template <typename CALL_TYPE>
    void __call(CALL_TYPE p_in)
    { __func(__dummy, p_in); };

And now I get the correct error:


Both calls now are ambiguous:

    test_int_tpl.__call(_int);        
    test_int_tpl.__call(c_int);           


My current conclusion is, its probably easier to exploit this "inconsistency" than to get it fixed, although I personally would like to see it being fixed, enabling the compiler to select the most likely possible match, preferring the reference type nad falling back to the value type, if references are not matching.


EDIT:

Maximum confusion at the moment.....

template <typename USER_DATA>
struct _test
{
    USER_DATA __dummy;


    template <typename UNIFIED>
    const bool __sub(const UNIFIED* p_in)
    { return((p_in != NULL) ? (__dummy > p_in) : true); };
    
    template <typename UNIFIED>
    const bool __sub(const UNIFIED& p_in)
    { return(__dummy > p_in); };



//    void __func(USER_DATA& p_out, USER_DATA& p_in)
//    { __sub(p_in); };
    
    void __func(USER_DATA& p_out, USER_DATA& p_in)
    { __sub(p_in); };
    

    template <typename TARGET_TYPE, typename SOURCE_TYPE>
    void __func(TARGET_TYPE*& p_out, SOURCE_TYPE* p_in)
    { __sub(dynamic_cast<const TARGET_TYPE*>(p_in)); };
    
    template <typename TARGET_TYPE, typename SOURCE_TYPE>
    void __func(TARGET_TYPE& p_out, SOURCE_TYPE* p_in)
    { __sub(dynamic_cast<const TARGET_TYPE*>(p_in)); };
    
    template <typename TARGET_TYPE, typename SOURCE_TYPE>
    void __func(TARGET_TYPE*& p_out, SOURCE_TYPE& p_in)
    { __sub(dynamic_cast<const TARGET_TYPE*>(&p_in)); };
    
    template <typename TARGET_TYPE, typename SOURCE_TYPE>
    void __func(TARGET_TYPE& p_out, SOURCE_TYPE& p_in)
    { __sub(TARGET_TYPE(p_in)); };
    
    template <typename TARGET_TYPE, typename SOURCE_TYPE>
    void __func(TARGET_TYPE& p_out, SOURCE_TYPE p_in)
    { __sub(p_in); };


    template <typename CALL_TYPE>
    void __call(CALL_TYPE* p_in)
    { __func(__dummy, p_in); };

    template <typename CALL_TYPE>
    void __call(CALL_TYPE& p_in)
    { __func(__dummy, p_in); };

    template <typename CALL_TYPE>
    void __call(const CALL_TYPE& p_in)
    { __func(__dummy, p_in); };

    template <typename CALL_TYPE>
    void __call(const CALL_TYPE p_in)
    { __func(__dummy, p_in); };

};




void OnStart()
{
    int             target_int;
    int             _int        = 5;
    const int       c_int       = 8;

    _test<int> test_int_tpl;

    test_int_tpl.__func(target_int, 7);
    test_int_tpl.__func(target_int, _int);
    test_int_tpl.__func(target_int, c_int);


    test_int_tpl.__call(7);           
    test_int_tpl.__call((int)7);      
    test_int_tpl.__call((const int)7);      
    test_int_tpl.__call(_int);        
    test_int_tpl.__call((int)_int);   
    test_int_tpl.__call((const int)_int); 
    test_int_tpl.__call(c_int);           
    test_int_tpl.__call((int)c_int);      
    test_int_tpl.__call((const int)c_int);

}


Why is the compiler not complaining about __func(), but only about __call()...

These three function calls

    test_int_tpl.__func(target_int, 7);
    test_int_tpl.__func(target_int, _int);
    test_int_tpl.__func(target_int, c_int);

Should match following templates:

    template <typename TARGET_TYPE, typename SOURCE_TYPE>
    void __func(TARGET_TYPE& p_out, SOURCE_TYPE& p_in)
    { __sub(TARGET_TYPE(p_in)); };
    
    template <typename TARGET_TYPE, typename SOURCE_TYPE>
    void __func(TARGET_TYPE& p_out, SOURCE_TYPE p_in)
    { __sub(p_in); };

Which they do.


But if that is the case, why dont these function calls

    test_int_tpl.__call(_int);        
    test_int_tpl.__call(c_int);           

Match these templates:


    template <typename CALL_TYPE>
    void __call(const CALL_TYPE& p_in)
    { __func(__dummy, p_in); };

    template <typename CALL_TYPE>
    void __call(const CALL_TYPE p_in)
    { __func(__dummy, p_in); };


And why is the call to __func() not ambiguous???

EDIT 2:

And why isnt this template overriding:

    template <typename TARGET_TYPE, typename SOURCE_TYPE>
    void __func(TARGET_TYPE& p_out, SOURCE_TYPE& p_in)
    { __sub(TARGET_TYPE(p_in)); };
    
    template <typename TARGET_TYPE, typename SOURCE_TYPE>
    void __func(TARGET_TYPE& p_out, SOURCE_TYPE p_in)
    { __sub(p_in); };


While this is:

    template <typename CALL_TYPE>
    void __call(CALL_TYPE& p_in)
    { __func(__dummy, p_in); };

    template <typename CALL_TYPE>
    void __call(CALL_TYPE p_in)
    { __func(__dummy, p_in); };


And this is not:

    template <typename CALL_TYPE>
    void __call(const CALL_TYPE& p_in)
    { __func(__dummy, p_in); };

    template <typename CALL_TYPE>
    void __call(const CALL_TYPE p_in)
    { __func(__dummy, p_in); };


Something strange is going on here

 
Dominik Christian Egert #:

Well it's hard to answer to such a long post, probably the longest I have ever seen on this forum

You should have cut it in several posts.

And I am getting confused again.

 
Alain Verleyen #:

Well it's hard to answer to such a long post, probably the longest I have ever seen on this forum

You should have cut it in several posts.

And I am getting confused again.

Well, i suggest to just focus on the EDIT and EDIT2 part of the post....

I could have split it up... - Its a complicated issue and its not that easy to understand (for me).

 
Dominik Christian Egert #:

Well, i suggest to just focus on the EDIT and EDIT2 part of the post....

I could have split it up... - Its a complicated issue and its not that easy to understand (for me).

No worries it's complicated for me as well, there is no standard behaviour to compare it due to the difference with C++.

I am trying to deduce a logic from analysis of the results, and we will see step by step if this logic continue to match until the end, or if there are some incoherences. The final goal is to either find something wrong in the logic, if the MQ choices are by design (on purpose) or by "chance" (random). And if there are obvious bugs.

I am still trying to find a logic about your first comparison which is not obvious. Yesterday I missed it, because I was using 3 versions of the template, ref, const ref, and value. Then both int and const it are ambiguous, all ok so I didn't check further.

 
Dominik Christian Egert #:

I see... - You really took your time to dig into this, thank you for the effort and obviously you fond something, I didnt see.

Thank you to push me to dig into this. It's very useful.
I dont think this is intended behaviour, or what the hell am I missing here???

At least, shouldnt it be exactly the other way around?

Work in progress...

Let see your first comparison.

  • In first case (both no const in the template to be clear, actually it gives the same results with no const/const) :
 1a. This call gives ambiguity.
    test_int_tpl.__call(_int);        

The templates are deduced to :

    void __call(int& p_in)
    void __call(int p_in)

This is ambiguous because there is no way to know which version you want to pick, by reference or by value ? the result would not be the same.  With no const/const instead of both no const, the logic is the same as "int p_in" and "const int p_in" are exactly the same from a caller perspective. Seems logical to me.

1b. This call in particular doesn't give ambiguity.

    test_int_tpl.__call(c_int);  

The templates are deduced to :

    void __call(const int& p_in)
    void __call(const int p_in)   // This one is used

This is not ambiguous because there is no difference (same result from caller perspective, it's a const) AND the "by value" has higher priority (to confirm ?).


  • In second case (const and non-const in the template to be clear, it gives the same results with both const), here it effectively started to become tricky :
2a. This call gives ambiguity.
   test_int_tpl.__call(c_int);

The templates are deduced to :

    void __call(const int& p_in)   
    void __call(const int p_in)         

This is ambiguous because ... (logic to find ?!?)

2b. This call in particular doesn't give ambiguity.

   test_int_tpl.__call(_int);

The templates are deduced to :

    void __call(const int& p_in)
    void __call(int p_in)        // This one is used, adding/removing const doesn't change anything
You can apply same logic as 1b (but then why is 2a ambiguous ?). This is not ambiguous because there is no difference (same result from caller perspective) AND the "by value" has higher priority (to confirm ?).
 
Alain Verleyen #:
Thank you to push me to dig into this. It's very useful.

Work in progress...

Let see your first comparison.

  • In first case (both no const in the template to be clear, actually it gives the same results with no const/const) :
 1a. This call gives ambiguity.

The templates are deduced to :

This is ambiguous because there is no way to know which version you want to pick, by reference or by value ? the result would not be the same.  With no const/const instead of both no const, the logic is the same as "int p_in" and "const int p_in" are exactly the same from a caller perspective. Seems logical to me.

1b. This call in particular doesn't give ambiguity.

The templates are deduced to :

This is not ambiguous because there is no difference (same result from caller perspective, it's a const) AND the "by value" has higher priority (to confirm ?).


  • In second case (const and non-const in the template to be clear, it gives the same results with both const), here it effectively started to become tricky :
2a. This call gives ambiguity.

The templates are deduced to :

This is ambiguous because ... (logic to find ?!?)

2b. This call in particular doesn't give ambiguity.

The templates are deduced to :

You can apply same logic as 1b (but then why is 2a ambiguous ?). This is not ambiguous because there is no difference (same result from caller perspective) AND the "by value" has higher priority (to confirm ?).
I did read it, I have to go through this again...


 
Alain Verleyen #:
...

EDIT:

There is something incoherent or which I missed the logic behind it.

In the previous version of Dominik code :

While in last code version :

So the logic seems reversed and didn't lead to an ambiguity. The only difference is the first code is non member template functions, while the last is class member template functions.

That could be a bug.

This is not a bug and is coherent, the difference is because the __call template selected is also depending of the choice of __func used, and the selected __func is a non templated with "const USER_DATA& p_in". So this specific case is clear.

 
Alain Verleyen #:
Thank you to push me to dig into this. It's very useful.

Work in progress...

Let see your first comparison.

...

I continued the analysis of your long post, with this " But here comes the twist: ... "

This isn't something different from the first comparison, same issue. So my post #37 is already synthetizing it.

Next one :

And if so, then shouldnt it be possible to declare a template with and without const, providing a match for both cases like this:

    template <typename CALL_TYPE>
    void __call(CALL_TYPE p_in)
    { __func(__dummy, p_in); };

    template <typename CALL_TYPE>
    void __call(const CALL_TYPE p_in)
    { __func(__dummy, p_in); };

This is perfectly logic, this is not allowed at all, because it makes no sense, it's exactly the same, and the const is superfluous.

Then :

But why can I create a matching template like this:

    template <typename CALL_TYPE>
    void __call(CALL_TYPE& p_in)
    void __call(const CALL_TYPE& p_in)
    void __call(CALL_TYPE p_in)

Both calls now are ambiguous: ...

Yes this is perfectly clear (to me at least ).

So we come to your first EDIT:

Why is the compiler not complaining about __func(), but only about __call()...

But if that is the case, why dont these function calls

Match these templates:

Because the choice of templates is not the same. You have there 3 template versions for __Call which are also using __func, while for the __func() you have 1 non-template and 2 templates. That leads to the difference.

To terminate, I just don't understand your EDIT 2. It's unclear in what context you are talking.


So all in all, from my point of view, my post #37 is well summarizing the main issue. We need to get an explanation about it and don't waste more time until we have one. Everything else follows from there.

Reason: