Archived

This topic is now archived and is closed to further replies.

Help with template function parameters

This topic is 5566 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I cannot figure out why the code at the end of this source listing does not compile when I call it with a literal. Can anyone help? And if so, could we please proceed from the premise that null knows what he is doing so that we can actually give definitive answers to the problem he is asking about? Much obliged; no grudges.
  
# include <new>
# include <stdexcept>

using std::logic_error;

// construct - invokes constructor, optionally passes arguments


template <typename T>
  inline T* construct (void* address)
    { return new (address) T; }

template <typename T, typename P1>
  inline T* construct (void* address, P1& p1)
    { return new (address) T (p1); }

template <typename T, typename P1, typename P2>
  inline T* construct (void* address, P1& p1, P2& p2)
    { return new (address) T (p1, p2); }

// destruct - invokes destructor


template <typename T>
  inline void* destruct (T* object)
    { delete object; return object; }

// reconstruct - invokes destructor and then constructor in-place


template <typename T>
  inline T* reconstruct (T* object)
    { return construct (destruct (object)); }

template <typename T, typename P1>
  inline T* reconstruct (T* object, P1& p1)
    { return construct (destruct (object), p1); }

template <typename T, typename P1, typename P2>
  inline T* reconstruct (T* object, P1& p1, P2& p2)
    { return construct (destruct (object), p1, p2); }

// dynamic - a class that adds dynamic construction to its T


template <typename T> class dynamic
{
  char memory [sizeof (T)]; // TODO: fix memory alignment issues

  T*   object;              // TODO: implement copying


  void verify () { if (not object) throw logic_error (""); }
  void verify_null () { if (object) throw logic_error (""); }

public:
   dynamic () : object (0) {}
  ~dynamic ()              { if (object) ::destruct (object); }

  T* operator -> () { verify (); return  object; }
  T& operator  * () { verify (); return *object; }

  // construct


    void construct ()
      { verify_null (); object = ::construct <T> (memory); }

  template <typename P1>
    void construct (P1& p1)
      { verify_null (); object = ::construct <T> (memory, p1); }

  template <typename P1, typename P2>
    void construct (P1& p1, P2& p2)
      { verify_null (); object = ::construct <T> (memory, p1, p2); }

  // destruct


    void destruct ()
      { verify (); ::destruct (object); object = 0; }

  // reconstruct


    void reconstruct ()
      { destruct (); construct (); }

  template <typename P1>
    void reconstruct (P1& p1)
      { destruct (); construct (p1); }

  template <typename P1, typename P2>
    void reconstruct (P1& p1, P2& p2)
      { destruct (); construct (p1, p2); }
};

// example code illustrating problem


struct myclass { myclass (int n) {} };

const int five = 5;

int main ()
{
  dynamic <myclass> dm;

  dm.construct (five);
  //dm.construct (5) // <- this one wouldn''t work!!

}  

Share this post


Link to post
Share on other sites
Well, at a first glance i would say it was because you are expecting non-const references to your construct() member functions. But then again "five" is const, and therefore shouldn't work either. Whats the error message?

quote:

And if so, could we please proceed from the premise that null knows what he is doing so that we can actually give definitive answers to the problem he is asking about? Much obliged; no grudges.



Wow, attitude problem.


[edit]

Ok, i put your code into my compiler, and changed all references going into construct() members and global functions const. It compiled fine when using a literal.

[edited by - sark on September 11, 2002 8:21:28 PM]

Share this post


Link to post
Share on other sites
quote:
Original post by sark
Well, at a first glance i would say it was because you are expecting non-const references to your construct() member functions.

Passing a const value to a function expecting a non-const value is never a problem. Doing the inverse is.

I would suspect type ambiguity - is "5" a char, a float, an int, a double...? That''s why five works (it has a known type), but the literal doesn''t (it''s type cannot unambiguously be inferred). Try placing a type cast before it:
dm.construct((int)5); 

Share this post


Link to post
Share on other sites
It won''t work for 5 for the same reason this doesn''t compile:
const int &i = &5;

Try changing construct to use value semantics (lose the references).


Also, gcc fails to compile it even for the const int five;

The :: scope operator fails to make it use the global functions, and it attempts to use one of the construct template methods.

Placing the global construct functions in a namespace, and referring to that explicit name space inside the dynamic class resolved this. I''m inclined to think that''s a bug in gcc 2.95.3

Share this post


Link to post
Share on other sites
quote:
Passing a const value to a function expecting a non-const value is never a problem. Doing the inverse is.

This is true for values, in both directions (copy constructors and assignment operators take const references; const references can be bound to both const and non-const objects), but in this case references are being created, and it''s not true for references.

Thus, whilst one can say:

  
const int i = 0;
int j = i;
const int k = j;

without issue, one cannot say:

  
const int i = 0;
int& j = i;

although

  
int j = 0;
const int& k = j;

is fine.

quote:
I would suspect type ambiguity - is "5" a char, a float, an int, a double...?

An int. It''s well-defined:

The type of an integer literal depends on its form, value, and suffix. If it is decimal and has no suffix, it has
the first of these types in which its value can be represented: int, long int; if the value cannot be represented
as a long int, the behavior is undefined. If it is octal or hexadecimal and has no suffix, it has the
first of these types in which its value can be represented: int, unsigned int, long int, unsigned
long int. If it is suffixed by u or U, its type is the first of these types in which its value can be represented:
unsigned int, unsigned long int. If it is suffixed by l or L, its type is the first of these
types in which its value can be represented: long int, unsigned long int. If it is suffixed by ul,
lu, uL, Lu, Ul, lU, UL, or LU, its type is unsigned long int.


quote:
That''s why five works (it has a known type), but the literal doesn''t (it''s type cannot unambiguously be inferred).

Yes it can.

Share this post


Link to post
Share on other sites

sark was perfectly right, with both her diagnosis of the problem in the code and with null's attitude problem, especially as null was extremely arrogant measured by her knowledge of the C++ const semantics.

But forgive me, Oluseyi and Magmai, your answers sure point out that your understanding of the topic is even inferior tho null's, thereby almost sanctioning her prematurely arrogant statement.

Oluseyi said, passing non-const when const was expected wouldn't normally impose a problem, and she was right. But then she said this exact thing was happening here, and that was utterly wrong. An implicitly const reference was about to be passed when a non-const reference was expected, and that means passing const when non-const is expected and must not compile.

Magmai then wittily responded that leaving out the references might make everything plusgood. Dear Magmai, imagine T's copy construction was costly. In this case the chance to do it only once instead of twice would be lost as soon as pass-by-reference was used. And even worse, imagine P1 was a type different from T, and P1 didn't provide copy semantics at all. Then construction from P1 using the construct method would be prohibited, although T's semantics supported it!
I honestly hope you're not working as a professional C++ programmer, for your sake and you employer's as well.

Finally, dear null, you're messing with template members without having understood the basic semantics of the language you are using, int this case the rules of pass-by-reference. And still you dare be as arrogant? Shame, I say, shame on you.

Hopeful to have beaten you, dear null, in terms of arrogance,

sincerely yours,

shadi


P.S. sark, you're the good one.

[edited by - shadi on September 11, 2002 9:28:31 PM]

Share this post


Link to post
Share on other sites
It doesn''t compile because you are missing a semi colon?

//dm.construct (5) // <- this one wouldn''t work!!


Joke aside. But even VC++ 6 compiles it fine. Did you edit the post or something? But I getting heap assertions failures when executing.

Share this post


Link to post
Share on other sites
quote:
Wow, attitude problem.


I waded through 150 posts of anti-American crud on another site supposedly dedicating a thread to remembering those who died last year in the terrorist attack, then I checked my other thread (which is still just a bunch of "are you sure you want to do this" posts), and then I created this topic. No one here seems to understand the problem, but one of DrPizza''s comments made me realize why my code does not work as I intended.


quote:
Ok, i put your code into my compiler, and changed all references going into construct() members and global functions const. It compiled fine when using a literal.


That''s fine, but I want the function to work with const and non-const literals and references. Which, I realize, is a rather tall order.


quote:
Passing a const value to a function expecting a non-const value is never a problem. Doing the inverse is.


The function templates are not even taking arguments by value.


quote:
It won''t work for 5 for the same reason this doesn''t compile:
const int &i = &5;

Try changing construct to use value semantics (lose the references).


Oh, now I''m *supposed* to pass by value.


quote:
An int.


So that is the problem! Any idea why it is not *const* int? That would solve my problem. Of course, I could try providing overloads for all of the intrinsic types, but I think that would require a lot of redundant code.


quote:
sark was perfectly right, with both her diagnosis of the problem in the code and with null''s attitude problem, especially as null was extremely arrogant measured by her knowledge of the C++ const semantics.


Rendering judgments from on high now, is it?

The compiler is supposed to detect whether the typename P1 is "int" or "const int." That is why passing a named constant works fine but not a literal. A literal''s type is "int", not "const int", so it P1& gets detected as "int&" to which we obviously cannot assign a literal. sark post did not provide any information that I did not already know, nor did you.

In the future if you desire an explanation, please refrain from narrating the thread.


quote:
But forgive me, Oluseyi and Magmai, your answers sure point out that your understanding of the topic is even inferior tho null''s, thereby almost sanctioning her prematurely arrogant statement.


(Why is everyone a "she" all of a sudden?)


quote:
Finally, dear null, you''re messing with template members without having understood the basic semantics of the language you are using, int this case the rules of pass-by-reference. And still you dare be as arrogant? Shame, I say, shame on you.


...I thought the Marx brothers were dead.


quote:
Hopeful to have beaten you, dear null, in terms of arrogance,


I agree completely. All hail the King of Pointless Titles!

Share this post


Link to post
Share on other sites
quote:
It doesn''t compile because you are missing a semi colon?


Alright, alright.


quote:
But even VC++ 6 compiles it fine.


You need to uncomment the line that reads:

//dm.construct (5) // <- this one wouldn''t work!! 


and then comment out the other call to construct.


quote:
But I getting heap assertions failures when executing.


AFAIK you should not, unless perhaps there is some funky alignment issue with that char array. Or you could be leaving both constructor calls in there, which might result in some debug assertions.

Share this post


Link to post
Share on other sites
quote:
Original post by null_pointer
I cannot figure out why the code at the end of this source listing does not compile when I call it with a literal.

The hidden irony of this is that I saw your commented line in the other thread and I nearly posted to mention why it wouldn't compile. Then I remembered the amount of whining I've had directed at me in these forums for posting about something off-thread and decided not to (not from you, null_pointer). This forum in particular is getting to the point where its really hard to post much at all without getting heavily criticised - no matter whether you are asking a question or trying to help, or even just trying to have a casual discussion, someone always interprets you as doing so in "the wrong way". It makes me wonder if its really worth the bother.

[edited by - SabreMan on September 12, 2002 4:46:28 AM]

Share this post


Link to post
Share on other sites
quote:
So that is the problem! Any idea why it is not *const* int? That would solve my problem. Of course, I could try providing overloads for all of the intrinsic types, but I think that would require a lot of redundant code.

No idea. In the section on literals, they''re described as merely int (etc.), as the quotation above demonstrates.

Elsewhere, it does state literals are all r-values, except for string literals, which are l-values. I can''t see why that should be the case -- I''m sure there''s some reason for letting them be l-values but I can''t see it off the top of my head.

Share this post


Link to post
Share on other sites
Add const in front of the reference, and then you can do it. You probably got that far already though, and it''s probably not what you want to hear

It SHOULD not compile with the const variable either, but probably it does some internal shenanigans to store that const var in memory (thereby allowing one to cast away const-ness and take the address).

Share this post


Link to post
Share on other sites
quote:
Original post by MadKeithV
Add const in front of the reference, and then you can do it. You probably got that far already though, and it''s probably not what you want to hear

It SHOULD not compile with the const variable either, but probably it does some internal shenanigans to store that const var in memory (thereby allowing one to cast away const-ness and take the address).



Yes, this is exactly what i thought.

After a little testing, i discovered you can pass a const variable to a templated function that expects a non-const reference - BUT if you attempt to modify the value within the function you get compiling errors. If you dont, its fine. Well, thats my compiler anyway (MSVC7).

Share this post


Link to post
Share on other sites
quote:
This forum in particular is getting to the point where its really hard to post much at all without getting heavily criticised - no matter whether you are asking a question or trying to help, or even just trying to have a casual discussion, someone always interprets you as doing so in "the wrong way". It makes me wonder if its really worth the bother.


I certainly hope that you do not stop posting. Your posts are always well-informed and important. Off hand I cannot remember any whining, but maybe I have been too busy lately to notice. Can you send me an email with some links? Too often newbies do not quite know enough yet to understand why the criticisms of their code can prove important, so they get upset.


quote:
No idea. In the section on literals, they're described as merely int (etc.), as the quotation above demonstrates.


I cannot imagine why literals would not be const. It makes no sense to be able to modify them. Oh, well...


quote:
Elsewhere, it does state literals are all r-values, except for string literals, which are l-values. I can't see why that should be the case -- I'm sure there's some reason for letting them be l-values but I can't see it off the top of my head.


Perhaps it has something to do with supporting the C statement:

char* p = "hello"; // <- should be const!  



quote:
Add const in front of the reference, and then you can do it. You probably got that far already though, and it's probably not what you want to hear


(Hmm...counting both threads, I'd say that makes five posts with this exact suggestion. No, maybe six.)

P1 is not supposed to be explicitly declared as const; the compiler is supposed to detect whether P1 is const or not. The only reason that it does not work is because the type of a literal is int, not const int as IMO it should be.


quote:
It SHOULD not compile with the const variable either, but probably it does some internal shenanigans to store that const var in memory (thereby allowing one to cast away const-ness and take the address).


No, not really. When a named constant is passed, P1 is properly detected as const int; therefore, P1& is const int&. Why? The const-ness is part of the type itself, so whether P1 is int or const int depends on what you pass to the function.

This really only matters in the situation I created. What I am trying to do is to create a function template that does some processing and then passes any number of arguments of any type to another function (in this case, T's constructor). It is similar in concept to a type-safe substitute for a va_arg_list.

To work correctly with all types of arguments, Px must be references. Had I used values, everything might as well be non-const anyway, but then T constructors expecting arguments by reference would silently get local temporaries from my function template. Ouch.


quote:
yes sabreman.... you know much of criticizing heavily, don't you? :D


Aw, give the guy a break, will ya?

[edited by - null_pointer on September 12, 2002 9:24:49 AM]

Share this post


Link to post
Share on other sites
I just caught another error in my code: the global reconstruct function should be using construct <T> instead of just construct because the global destruct returns a void pointer. That certainly explains why reconstruct had such odd error messages in some cases where construct and destruct would work fine.

Share this post


Link to post
Share on other sites
quote:
But I getting heap assertions failures when executing.


Ack! When I retyped the code, I put a call to delete in the destruct function. I should have been invoking the destructor directly with object->~T ().

Share this post


Link to post
Share on other sites
quote:
const and volatile. You seemed to think that the const-ness was part of the type. Well, normally it is, but template type arguments will strip them.


I do not see what you mean about template type arguments stripping the const and volative qualifiers. If I pass a const int to my construct function, P1 will be const int, and P1& will be const int&. Without this behavior, passing the named constant five would not work, but it does.

Share this post


Link to post
Share on other sites
quote:
Original post by null_pointer
I do not see what you mean about template type arguments stripping the const and volative qualifiers.

I mean the cv qualifiers get stripped.

That is, you can do this:

  
template <typename T>
void foo(T t)
{
T t2;
t2 = t; // this assignment is impossible if T is a const type

}

int main(void)
{
const int i = 0;
int j = 0;
foo(i); // the deduced type is not const, even though i is.

foo(j);
}


If the cv qualifiers were kept, foo(i) would fail -- it''d try to assign to a const int which obviously isn''t allowed.

But it doesn''t. Why? Because T is int in both calls.

quote:
If I pass a const int to my construct function, P1 will be const int,

No it won''t. cv qualifiers are stripped.

quote:
and P1& will be const int&.

No it won''t. cv qualifiers are stripped.

quote:
Without this behavior, passing the named constant five would not work, but it does.

With this behaviour, the above code would not work. It does work.

Now, if you specify the type explicitly (that is, foo<const int>(i);) then I think that the type is indeed const (I haven''t checked the standard, but both g++ and Comeau will treat that as an error, though VC++ doesn''t).

Share this post


Link to post
Share on other sites
quote:
If the cv qualifiers were kept, foo(i) would fail -- it''d try to assign to a const int which obviously isn''t allowed.

But it doesn''t. Why? Because T is int in both calls.


Change foo to take a reference to T. Suddenly, you get the following errors:

error C2734: ''t2'' : const object must be initialized if not extern
error C2166: l-value specifies const object


This tells me that T is indeed const, because t2 which is const is declared as:

T t2; 


The conclusion is that cv-qualifiers are only stripped if the function template parameter is not a reference or pointer type. Which is what I am doing with my construct function. Which is why my P1 *is* const int, and my P1& *is* const int&, exactly as I described in an earlier post.

Share this post


Link to post
Share on other sites
quote:
Now, if you specify the type explicitly (that is, foo(i) then I think that the type is indeed const (I haven''t checked the standard, but both g++ and Comeau will treat that as an error, though VC++ doesn''t).


I know that you can definitely do that with class templates (i.e., std::auto_ptr <const CWnd> creates a pointer to a const CWnd object), but I have no idea if it can be done with function templates.

Share this post


Link to post
Share on other sites