1

How can one emulate a constructor for a class template with static members only and force specializations of the constructor?

I want to achieve an emulated "static constructor" but with class templates and without un-specialized constructor (function template). These functions must be implicitly called (although it must be handled with care) and a separate function must be defined for each class template instantiation.

Also I want to separate the (1) definition of the class (and everything else necessary for the constructor emulation) from the (2) template instantiation and specialized "constructor" definition. The (1) part should have no dependency to the (2).

The operations by the "constructor" may only effect global static memory.

Attempt 1 "ripped"

Here is what I have so far:


#include <iostream>
using namespace std;

// -------------------- BEGIN PART (1)
template<int N> struct S
{
  struct IS {  };
  static IS init();
  static IS is;
  static void foo() { cout << "foo called by S<" << N << ">" << endl; }
};
// -------------------- END PART (1)

// -------------------- BEGIN PART (2)
template<> S<1>::IS S<1>::init() // "constructor" specialized for "1"
{
    cout << "init called by S<1>" << endl;
    // do stuff specific to "1"
    return {};
}

template<> S<3>::IS S<3>::init() // "constructor" specialized for "3"
{
    cout << "init called by S<3>" << endl;
    // do stuff specific to "3"
    return {};
}
// -------------------- END PART (2)

// -------------------- BEGIN PART (1)
template<> S<1>::IS S<1>::is = init();
// -------------------- END PART (1)

int main()
{
    cout << "Hello, Wandbox!" << endl;
    S<3>::foo();
}

Output:

init called by S<1>
Hello, Wandbox!
foo called by S<3>

In this case I need an error from the compiler / linker that tells the programmer that S<3>::is is undefined. The point is to make sure the initialization has not been forgotten by mistake.

Attempt 2 "order problem"

Here the class definition and the usage specific are separated as desired. But it can not compile.

structure.hpp
template<int N> struct S
{
  struct IS {  };
  static IS init();
  static IS is;
};

template<> S<1>::IS S<1>::is = init();
template<> S<2>::IS S<2>::is = init();
main.cpp
#include <iostream>
#include "structure.hpp"
using namespace std;

template<> S<1>::IS S<1>::init() // "constructor" specialized for "1"
{
    cout << "init called by S<1>" << endl;
    // do stuff specific to "1"
    return {};
}

template<> S<3>::IS S<3>::init() // "constructor" specialized for "3"
{
    cout << "init called by S<3>" << endl;
    // do stuff specific to "3"
    return {};
}

int main()
{
    cout << "Hello, Wandbox!" << endl;
}

Compilation error:

error: specialization of 'static S<N>::IS S<N>::init() [with int N = 1]' after instantiation
    5 | template<> S<1>::IS S<1>::init() // "constructor" specialized for "1"
      |                                ^
user5534993
  • 518
  • 2
  • 17
  • You never reference `S<3>::is`, so the linker doesn't need to look for it. If it is referenced in code, then you should get an error message sine you don't define it. – 1201ProgramAlarm Mar 05 '20 at 16:59
  • @1201ProgramAlarm: I would like to have it implicit somehow. `S<3>::is`s only purpose is to enforce initialization. – user5534993 Mar 05 '20 at 17:09
  • Does the initialization have to happen before main starts (i.e., as a global variable), or can it be done the first time `foo` is called? – 1201ProgramAlarm Mar 05 '20 at 20:00
  • @1201ProgramAlarm: The initialization must take place before main starts. – user5534993 Mar 05 '20 at 21:11

1 Answers1

0

This solutions uses a non-static class template as a container for the (static) class (template) to add a constructor to it.The downside of this solution is, that:

  • The original (contained) class (template) can still be accessed without a "constructor"
  • Accessing the functions is not static anymore. The address of the static functions must be dereferenced - potentially at runtime.
  • Access of the original static functions must be done through the member (ugly syntax)
#include <iostream>
using namespace std;

//! adds a constructor to a static class
template<typename T>
struct Container
{
    Container();    // no generic definition   
    T s;            //!< non-static member of (static) class
};

template<int N>
struct S
{
    static void foo()
    {
        cout << "foo " << N << endl;
    }
};

using T1 = Container<S<42>>;
using T2 = Container<S<21>>;

// ---------- individual constructors for the contained classes 
template<> T1::Container() { cout << "init 42" << endl; }
template<> T2::Container() { cout << "init 21" << endl; }

// ---------- object instantiation
T1 w1;          // it does not matter if the object is
static T2 w2;   // static or not

int main() {
  std::cout << __cplusplus << std::endl;    

  w1.s.foo();
  w2.s.foo();  
}

Especially because the original classifier can still be used without enforcing the "constructor" to be called (at initialization time), this solution is not satisfying.

user5534993
  • 518
  • 2
  • 17
  • does this answer the question? (serious question, I didnt understand your question) – 463035818_is_not_an_ai Mar 05 '20 at 17:26
  • @idclev: i am not satisfied with the solution. Because it does not enforce the user to use the `W` structure. The user may still directly call the static functions. Also because it introduces an indirection level which must be potentially resolved at execution time. – user5534993 Mar 05 '20 at 21:23