Jump to content
  • Advertisement
xaxazak

C++ Allowing libraries to use user-specializations of their own templates.

Recommended Posts

NOTE: This is a cross-post, sorry. I asked this question (in a worse way) on StackOverflow, but it's not really a StackOverflow-type question.

---


Say you want to write a library, which you want to:

  1. Provide a user-specializable template type (eg CFoo).
  2. Provide a function that uses that type (eg: bar(CFoo& foo)) - this function needs to use any user specializations.
  3. Be included by a single header file.

I don't think it's possible to achieve all three, because specializations of CFoo need to occur after CFoo's declaration, but before bar()'s definition. If both are provided inside the same header, then there's no sane way to get user code between the two.

Looking at it another way, I'm looking for a way to delay compiling the definition of bar() until the user's code has been compiled.

---

The mechanisms I have considered to mitigate this problem are (copied (and tidied) from linked post):

  1. Attempting to craft some sort of single "late_call" forwarding template function, which is defined at the end of the source and uses some mechanism to deduce the target function from its parameters. Unfortunately, I can only see how this can work in very rare special cases.

    • [--] Mostly doesn't work.
    • [-] Requires a #include at the end of source.
  2. Creating an extendable list of headers to include, via the preprocessor, then including them all at the end via a single final #include. It's possible to hack a list like this with a fixed number of places using a lot of #defines.

    • [-] Artificial limit.
    • [-] Uses macro #includes, which screw up some tools.
    • [-] Ugly as hell.
    • [-] Requires a #include at the end of source.
  3. Creating my own manual pragma-type command, then writing an external tool to run over the preprocessed code and move stuff about before compiling.

    • [+] Works perfectly.
    • [+] Nothing needs to be added to the end of the source.
    • [--] This pretty much ensures nobody will ever want to use my library, and I'll probably hate it myself too.
  4. Creating a "late.hpp", in which I add #includes for every delayed definition, guarded by #ifdefs to check whether they're needed.

    • [-] Requires a #include at the end of source.
    • [--] Breaks modularity.
  5. Manually add a list of delayed-definition headers at the end.

    • [--] Breaks modularity. Source files may indirectly acquire new delayed definition requirements if other implementations change.
    • [-] Ugly.
    • [--] Potential source of bugs.

I'm currently using #4.

---

Does anyone have any better ideas?

NOTE: I'm especially interested in methods that would work for multiple independent libraries.

 

 

Share this post


Link to post
Share on other sites
Advertisement
2 hours ago, xaxazak said:

NOTE: This is a cross-post, sorry. I asked this question (in a worse way) on StackOverflow, but it's not really a StackOverflow-type question.

LOL! I think that's like every question on StackOverflow.  I couple of months ago I created an account asked one C++ question, and got immediately attacked by a few people probably because my rep rating was 0 and they figured I was a noob.  To be fair a few people did try to answer the question but the hostility was rather comical.  I deleted my account and came here.

As for your question I find it almost impossible to understand other people's explanations of their template issues.  I have template stuff with like 12 parameters that seems to only work because I'm using typedef  in places. It looks kind of cryptic.  I look at it and think if anyone else ever sees it, I'm never getting another job. In any case my first question is why isn't "bar" just a member of CFoo since it takes a CFoo as a parameter?  I guess I'm not getting the problem.

Share this post


Link to post
Share on other sites
3 hours ago, xaxazak said:

I don't think it's possible to achieve all three, because specializations of CFoo need to occur after CFoo's declaration, but before bar()'s definition. If both are provided inside the same header, then there's no sane way to get user code between the two.

I think you're wrong about the requirement that the definition that bar must come after the specializations of CFoo, at least if bar is a template function.  I tried this test program:

#include <iostream>

template<class T> class CFoo {
public:
  void f() { std::cout << "default implementation" << std::endl; }
};

template<class T> void bar(CFoo<T>& x) {
  x.f();
}

template<> class CFoo<int> {
public:
  void f() { std::cout << "specialized implementation" << std::endl; }
};

int main() {
  CFoo<int> x;
  bar(x);
  return EXIT_SUCCESS;
}

It printed "specialized implementation" as expected.  That's because the code for template functions is only generated at the point where the function is actually called.

Share this post


Link to post
Share on other sites
Posted (edited)

I'm afraid I totally screwed that up, my apologies.

I had this issue years ago and I'm only now working to tidy up the code. But it appears I forgot the details of the issue.

On 7/7/2018 at 11:05 PM, a light breeze said:

I think you're wrong about the requirement that the definition that bar must come after the specializations of CFoo, at least if bar is a template function. ...  That's because the code for template functions is only generated at the point where the function is actually called.

My issue is actually both specializations AND overloads. And the specializations are more (albeit not entirely) "obedient", so you're totally correct there.

I did have some issues with specializations too, but either in combination with overloads or in cases where I actually wanted to specialize after they'd been used (altering the literal-string specialization, except earlier assertions had already used it - which I'll agree was actually due to bad design).

So, I may as well just ask about overloads, since fixing that will also fix all related specialization issues.

 

And I should give an actual explicit code example, too. Toggle commenting out "#define USER_CODE_BETWEEN" to see the issue.

 

#include <iostream>

//#define USER_CODE_BETWEEN // <====--- Toggle This Line.


//=== INITIAL LIBRARY CODE ===//

template <typename T>
void foo(T const& arg)
    {
        std::cout << "default = " << arg << std::endl;
    }

//=== USER CODE (BETWEEN) ===//

#ifdef USER_CODE_BETWEEN
void foo(int const& arg)
    {
        std::cout << "overload = " << arg << std::endl;
    }
#endif

//=== FINAL LIBRARY CODE ===//

template <typename T>
void bar(T const& arg)
    {
        foo(arg);
    }

//=== USER CODE (AFTER) ===//

#ifdef USER_CODE_FIRST
void foo(int const& arg)
    {
        std::cout << "overload = " << arg << std::endl;
    }
#endif


int main()
    {
        char c = 'c';
        int i = 1;
        
        bar(c);
        bar(i);
        
        return EXIT_SUCCESS;
    }

USER_CODE_BETWEEN is how I want it to work, but that can't be achieved if there's only one header that includes both INITIAL LIBRARY CODE and FINAL LIBRARY CODE.

Edited by xaxazak
extra sentence at bottom

Share this post


Link to post
Share on other sites

So, use template specialization instead of overloads?  You can specialize functions.  The following works:

#include <iostream>


//=== INITIAL LIBRARY CODE ===//

template <typename T>
void foo(T const& arg)
    {
        std::cout << "default = " << arg << std::endl;
    }

//=== FINAL LIBRARY CODE ===//

template <typename T>
void bar(T const& arg)
    {
        foo(arg);
    }

//=== USER CODE (AFTER) ===//

template<>
void foo<int>(int const& arg)
    {
        std::cout << "overload = " << arg << std::endl;
    }


int main()
    {
        char c = 'c';
        int i = 1;
        
        bar(c);
        bar(i);
        
        return EXIT_SUCCESS;
    }

 

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!