template problem: local variable as non-type argument

  • Follow


I know the reason for the problem. Template instanziation happens at compile
time.
So it cannot handle function parameter of createInstance function at
runtime.

The question is: how shall I realize it differently as follows [pseudo
code]:
     if(i == 1)
         return C<1>();
     if(i == 2)
         return C<2>();
     // ... maintenance nightmare ...

#include <iostream>
class Base {
public:
     virtual void foo() = 0;
};

template<int T>
class C : public Base {
public:
     virtual void foo() { /* default does nothing */ }
};

template<>
class C<1> : public Base {
public:
     virtual void foo() { std::cout << "C<1>::foo "; }
};

template<>
class C<2> : public Base {
public:
     virtual void foo() { std::cout << "C<2>::foo "; }
};

class Factory {
public:
     static Base& createInstance(int i);
};

Base& Factory::createInstance(int i) {
     // PROBLEM:
     // error C2971: 'C' : template parameter 'T' : 'i' : a local variable
cannot be used as
     // a non-type argument
     // WORKS: int const val = 0;
     return C<i>(); //ignore warning: returning address of local variable or
temporary
}

void main() {
     Base& anInstance = Factory::createInstance(1);
     Base& anotherInstance = Factory::createInstance(2);
}



-- 
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply vl106 2/5/2009 2:34:09 AM

On 5 фев, 11:34, "vl106" <vl...@hotmail.com> wrote:
> Base& Factory::createInstance(int i) {
>      // PROBLEM:
>      // error C2971: 'C' : template parameter 'T' : 'i' : a local variable
> cannot be used as
>      // a non-type argument
>      // WORKS: int const val = 0;
>      return C<i>(); //ignore warning: returning address of local variable or
> temporary
>
> }

Option 1, "switch" statement is much faster than "if" one:
Base& Factory::createInstance(int i) {
     switch(i){
         case 1: return C<1>();
         case 2: return C<2>();
         ...
     }
}

Option 2, linear recursive helper function:
const int max_n = 100;
template<int n>
struct helper
{
     static Base& func(int i)
     {
         return i == n? C<n>() : helper<n+1>::func(i);
     }
};

template<>
struct helper<max_n>
{
     static Base& func(int i)
     {
         return C<max_n>();
     }
};

Base& Factory::createInstance(int i) {
     return helper<0>(i);
}

Option 3, similar to Option 1 but using Boost.Preprocessor library you
can avoid manual array completion and functions writing:

// the following functions can be generated by Boost.Preprocessor
(http://www.boost.org/doc/libs/1_37_0/libs/preprocessor/doc/
index.html), takes 10 minutes to learn, 5 minutes to write generator
for hungred functions.

Base& func0()
{
     return C<0>();
}

Base& func1()
{
     return C<1>();
}

Base& func2()
{
     return C<2>();
}
....

Base& Factory::createInstance(int i) {
     const Base& (*ptrs)()[]={func0,func1,func2,... /* can be filled by
Boost.Preprocessor too */};
     return ptrs[i]();
}


-- 
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply potapov 2/5/2009 8:05:27 AM


"vl106" <vl106@hotmail.com> writes:

> The question is: how shall I realize it differently as follows [pseudo
> code]:
>      if(i == 1)
>          return C<1>();
>      if(i == 2)
>          return C<2>();
>      // ... maintenance nightmare ...

vs

> Base& Factory::createInstance(int i) {
>      // PROBLEM:
>      // error C2971: 'C' : template parameter 'T' : 'i' : a local variable
> cannot be used as
>      // a non-type argument
>      // WORKS: int const val = 0;
>      return C<i>(); //ignore warning: returning address of local variable or
> temporary

That's a rather serious warning you're ignoring.

> }
>
> void main() {
>      Base& anInstance = Factory::createInstance(1);
>      Base& anotherInstance = Factory::createInstance(2);
> }

What is it you are actually trying to do? How do C<1> and C<2> differ?
Do all the C<some-int-value> specializations actually exist and make
sense, or only those you've explicitly specialized?

Without further information, if the list is limited, I'd go with the
switch. You could probably remove the duplication with some preprocessor
magic such as that in the Boost Preprocessor library.

Anthony
-- 
Author of C++ Concurrency in Action | http://www.manning.com/williams
just::thread C++0x thread library   | http://www.stdthread.co.uk
Just Software Solutions Ltd         | http://www.justsoftwaresolutions.co.uk
15 Carrallack Mews, St Just, Cornwall, TR19 7UL, UK. Company No. 5478976

      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply Anthony 2/5/2009 8:05:36 AM

On 5 Feb., 09:34, "vl106" <vl...@hotmail.com> wrote:
> I know the reason for the problem. Template instanziation happens at compile
> time.
> So it cannot handle function parameter of createInstance function at
> runtime.
>
> The question is: how shall I realize it differently as follows [pseudo
> code]:
>      if(i == 1)
>          return C<1>();
>      if(i == 2)
>          return C<2>();
>      // ... maintenance nightmare ...

I'd say it depends on what you are trying to achieve.

> #include <iostream>
> class Base {
> public:
>      virtual void foo() = 0;
>
> };
>
> template<int T>
> class C : public Base {
> public:
>      virtual void foo() { /* default does nothing */ }
>
> };
>
> template<>
> class C<1> : public Base {
> public:
>      virtual void foo() { std::cout << "C<1>::foo "; }
>
> };
>
> template<>
> class C<2> : public Base {
> public:
>      virtual void foo() { std::cout << "C<2>::foo "; }
>
> };
>
> class Factory {
> public:
>      static Base& createInstance(int i);
>
> };

why a static class member function?

> Base& Factory::createInstance(int i) {
>      // PROBLEM:
>      // error C2971: 'C' : template parameter 'T' : 'i' : a local variable
> cannot be used as
>      // a non-type argument
>      // WORKS: int const val = 0;
>      return C<i>(); //ignore warning: returning address of local variable or
> temporary
> }

Your factory approach is flawed. You're returning a reference to a
local variable which becomes a dangling reference.

> void main() {
>      Base& anInstance = Factory::createInstance(1);
>      Base& anotherInstance = Factory::createInstance(2);
>
> }

The function 'main' needs to return an int.

Here the arguments to createInstance are constant expressions. So,
technically you could convert createInstance to a function template.

    template<int I>
    C<I> createInstance() { return C<I>(); }

    int main() {
      const Base& b = createInstance<2>();
      b.some_virtual_const_function();
    }

But what would be the point of it? It looks like you want runtime
polymorphism. Why don't you simply make the int a parameter to the
constructor?

   // lib.hh

   #include <memory>

   class Base {
   public:
      virtual void foo() = 0;
      virtual ~Base() {}
   };

   std::auto_ptr<Base> factory(int);

   // lib.cc

   #include <iostream>
   #include <ostream>
   #include <memory>

   class C : public Base {
     int myint;
   public:
     explicit C (int i) : myint(i) {}
     ~C() {}
     void foo();
   };

   void C::foo() {
     std::cout << "C::foo says " << myint << std::endl;
   }

   std::auto_ptr<Base> factory(int i) {
     std::auto_ptr<Base> ap (new C(i));
     return ap;
   }

   // main.cc

   #include <memory>

   int main() {
     std::auto_ptr<Base> apb = factory(2);
     apb->foo();
   }

Be sure to check out related C++ idioms: handle/body, counted body,
envelope/letter, ...

Cheers!
SG


-- 
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply SG 2/5/2009 8:05:44 AM

I have to better describe the problem:

[kind of pseudo code]
void receive_message(int messageId, char* data) {
    Base& new_message = Factory::createMessage(messageId, data); //
transition to C++
    new_message.process();
}

>From old, non-C++ library I receive messages identified by int id.
I want to map this "event" into an "object-oriented" message. Further
receive_message should stay stable. That is when a new kind of message
is received I only want to create a new specialization
(C<new_message_id>).
This specialization will have a process method with different
behaviour.

-- 
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply vl106 2/6/2009 7:38:43 AM

vl106 <vl106@hotmail.com> writes:

> I have to better describe the problem:
>
> [kind of pseudo code]
> void receive_message(int messageId, char* data) {
>     Base& new_message = Factory::createMessage(messageId, data); //
> transition to C++
>     new_message.process();
> }
>
>>From old, non-C++ library I receive messages identified by int id.
> I want to map this "event" into an "object-oriented" message. Further
> receive_message should stay stable. That is when a new kind of message
> is received I only want to create a new specialization
> (C<new_message_id>).
> This specialization will have a process method with different
> behaviour.

Why do you need a specialization? Why can't you just define a normal
class derived from Base? Once you're out of createMessage you can't
access the derived class anyway unless you know what it is.

I would just have createMessage do a lookup of ID to class to create,
either using a switch statement or a lookup table.

Anthony
-- 
Author of C++ Concurrency in Action | http://www.manning.com/williams
just::thread C++0x thread library   | http://www.stdthread.co.uk
Just Software Solutions Ltd         | http://www.justsoftwaresolutions.co.uk
15 Carrallack Mews, St Just, Cornwall, TR19 7UL, UK. Company No. 5478976

      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply Anthony 2/6/2009 12:33:18 PM

On 6 Feb., 14:38, vl106 <vl...@hotmail.com> wrote:
> I want to map this "event" into an "object-oriented" message. Further
> receive_message should stay stable. That is when a new kind of message
> is received I only want to create a new specialization
> (C<new_message_id>).
> This specialization will have a process method with different
> behaviour.

You can't instantiate a class template with runtime parameters.
Templates won't help you here. There's no way around checking the ID
at runtime -- via
1. if/else if/else  or
2. switch/case  or
3. a map that maps message ID to a specific message factory.

I recently had a similar case and went with the 3rd approach. It only
requires the factories to register themselves at program start up
(plug-in like). So, after writing your new message class and its
factory you only have to add this factory to the map.

Cheers!
SG

-- 
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply SG 2/6/2009 12:34:36 PM

potapov.d@gmail.com wrote:
> 
> Option 1, "switch" statement is much faster than "if" one:

Are you sure? I'd say, the "switch" statement *may* be faster,
but it all depends on the details of the implementation;
for example, the "switch" statement may be implemented
as a series of the "if"..."else"... statements.

-- 
Seungbeom Kim

      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply Seungbeom 2/7/2009 11:45:11 AM

On 7 ���, 20:45, Seungbeom Kim <musip...@bawi.org> wrote:
> Are you sure? I'd say, the "switch" statement *may* be faster,
> but it all depends on the details of the implementation;
> for example, the "switch" statement may be implemented
> as a series of the "if"..."else"... statements.

We're speaking about compilers here, not the hand-made trash, right?
So, concerning compilers, would you be so kind to provide the link to
one, that use the series of "if ... else ..." instead of "switch"? I
can confirm that VC++6.0 (or later) and GCC 4.1 (or later) doesn't.


--
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply potapov 2/8/2009 4:22:35 PM

<potapov.d@gmail.com>
>> Are you sure? I'd say, the "switch" statement *may* be faster,
>> but it all depends on the details of the implementation;
>> for example, the "switch" statement may be implemented
>> as a series of the "if"..."else"... statements.
>
> We're speaking about compilers here, not the hand-made trash, right?
> So, concerning compilers, would you be so kind to provide the link to
> one, that use the series of "if ... else ..." instead of "switch"? I
> can confirm that VC++6.0 (or later) and GCC 4.1 (or later) doesn't.

The compilers did a great job optimizing switch using many forms --
including chained if, table fragments, full table -- chosing by the actual
composition of case labels. MSC 6 (not Visual, not C++ ;-)  did it ~20 years
ago.

I wouldn't worry about code generated from such constructs until there's
strong evidence of failure...


-- 
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply Balog 2/8/2009 10:38:23 PM

potapov.d@gmail.com wrote:
> On 7 Feb, 20:45, Seungbeom Kim <musip...@bawi.org> wrote:
>> Are you sure? I'd say, the "switch" statement *may* be faster,
>> but it all depends on the details of the implementation;
>> for example, the "switch" statement may be implemented
>> as a series of the "if"..."else"... statements.
> 
> We're speaking about compilers here, not the hand-made trash, right?

We're talking about the language here, not compilers.
And the language does not specify how the statements should be
implemented, nor whether one should be faster than the other.
The compiler is free to choose whatever method to do the job.

> So, concerning compilers, would you be so kind to provide the link to
> one, that use the series of "if ... else ..." instead of "switch"? I
> can confirm that VC++6.0 (or later) and GCC 4.1 (or later) doesn't.

Having said the above, it doesn't matter that much, but I tried anyway:

void using_if(int n)
{
     if (n == 0)
         puts("zero");
     else if (n == 1)
         puts("one");
     else
         puts("other");
}

void using_switch(int n)
{
     switch (n) {
     case 0:
         puts("zero"); break;
     case 1:
         puts("one"); break;
     default:
         puts("other"); break;
     }
}

and GCC 4.1.3 on x86_64 produces chained branches for both.
(Actually, the switch statement produces a somewhat longer code
for a reason I don't obviously understand.)

-- 
Seungbeom Kim

      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply Seungbeom 2/9/2009 5:09:59 AM

On Feb 5, 6:05 am, potapo...@gmail.com wrote:
>
> Option 1, "switch" statement is much faster than "if" one:
> Base& Factory::createInstance(int i) {
>      switch(i){
>          case 1: return C<1>();
>          case 2: return C<2>();
>          ...
>      }
>
> }
>
> Option 2, linear recursive helper function:
> const int max_n = 100;
> template<int n>
> struct helper
> {
>      static Base& func(int i)
>      {
>          return i == n? C<n>() : helper<n+1>::func(i);
>      }
>
> };
>
> template<>
> struct helper<max_n>
> {
>      static Base& func(int i)
>      {
>          return C<max_n>();
>      }
>
> };
>
> Base& Factory::createInstance(int i) {
>      return helper<0>(i);
>
> }
>
<snip>
>
> Base& func0()
> {
>      return C<0>();
>
> }
>
> Base& func1()
> {
>      return C<1>();
>
> }
>
> Base& func2()
> {
>      return C<2>();}
>
> ...
>
> Base& Factory::createInstance(int i) {
>      const Base& (*ptrs)()[]={func0,func1,func2,... /* can be filled by
> Boost.Preprocessor too */};
>      return ptrs[i]();
>
> }
>

Unfortunately all of those examples fail as they are returning
references to locals that go out of scope.

An appropriate fix for all of them, including the helper version which
I quite like, is to make a local static object like this:

Base & func0()
{
   static const C<0> item;
   return item;
}

BTW this pattern is very useful in cases where you benchmarked your
code and absolutely need the optimization of template specializations
to factor out variable coefficients into constants.

--jeffk++
www.jdkoftinoff.com


-- 
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply Jeff 2/9/2009 5:12:19 AM

On Feb 9, 3:12 am, Jeff Koftinoff <jeff.koftin...@gmail.com> wrote:
> On Feb 5, 6:05 am, potapo...@gmail.com wrote:
>
>
>
>
>
> > Option 1, "switch" statement is much faster than "if" one:
> > Base& Factory::createInstance(int i) {
> >      switch(i){
> >          case 1: return C<1>();
> >          case 2: return C<2>();
> >          ...
> >      }
>
> > }
>
> > Option 2, linear recursive helper function:
> > const int max_n = 100;
> > template<int n>
> > struct helper
> > {
> >      static Base& func(int i)
> >      {
> >          return i == n? C<n>() : helper<n+1>::func(i);
> >      }
>
> > };
>
> > template<>
> > struct helper<max_n>
> > {
> >      static Base& func(int i)
> >      {
> >          return C<max_n>();
> >      }
>
> > };
>
> > Base& Factory::createInstance(int i) {
> >      return helper<0>(i);
>
> > }
>
> <snip>
>
> > Base& func0()
> > {
> >      return C<0>();
>
> > }
>
> > Base& func1()
> > {
> >      return C<1>();
>
> > }
>
> > Base& func2()
> > {
> >      return C<2>();}
>
> > ...
>
> > Base& Factory::createInstance(int i) {
> >      const Base& (*ptrs)()[]={func0,func1,func2,... /* can be filled by
> > Boost.Preprocessor too */};
> >      return ptrs[i]();
>
> > }
>
> Unfortunately all of those examples fail as they are returning
> references to locals that go out of scope.
>
> An appropriate fix for all of them, including the helper version which
> I quite like, is to make a local static object like this:
>
> Base & func0()
> {
>    static const C<0> item;
>    return item;
>
> }
>
> BTW this pattern is very useful in cases where you benchmarked your
> code and absolutely need the optimization of template specializations
> to factor out variable coefficients into constants.

Do note that the first calls to the above code fragment is not thread-
safe in most implementations of C++03. It will be thread-safe in C+
+0x.


-- 
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply joshuamaurice 2/9/2009 3:02:09 PM

12 Replies
135 Views

(page loaded in 0.661 seconds)

Similiar Articles:


















7/30/2012 8:20:56 AM


Reply: