rocktale
rocktale

Reputation: 331

C++ templates and function resolution

Here is a problem I stumbled accross when refactoring some code and I was wondering if there is a better way to do it:

#include <iostream>

template<typename T>
class Foo
{
public:
  Foo()
  {
    init(x);
  }

  T x;
};

void init(int& i)
{
  i = 42;
}

int main()
{
  Foo<int> foo;
  std::cout << foo.x << std::endl;
  return 0;
}

Unfortunately, this doesn't compile - neither with GCC or with Clang. The function init called in Foo's constructor is not declared. In this toy example, this could be solved by moving the function itself ahead of the template. However, in a more complex context, this may not work. Originally, I intended to use overloads of init to allow some setup for the classes used in the template.

I assumee that in this context init is a non-dependend name - even though the argument of the function call depends on the template parameter (which was odd for me at first). Is there a way to trick it to also consider function definitions defined after the template itself?

I know that I can use template specialization (which was in the original code in the first place, but I wanted to replace it with simpler overloads):

template<typename>
struct Initializer;

template<>
struct Initializer<int>
{
  static void init(int& i)
  {
    i = 42;
  }
}

Is there a way to make this work with function overloads as well? I know, boost::serialization also relies on function overloads for custom types, but I did not really find where and how they implemented that.

Upvotes: 0

Views: 115

Answers (2)

Jarod42
Jarod42

Reputation: 218148

Your problem is also that intis not a class and no ADL is done for it, replacing int by custom class works:

template<typename T>
class Foo
{
public:
  Foo() { init(x); }

  T x;
};

struct C
{
    int i = 0;    
};

void init(C& c) { c.i = 42; }

Demo.

To allow to works for primitive type as int, you have to introduce some custom type:

template <typename> struct tag {};

template<typename T>
class Foo
{
public:
  Foo() { init(x, tag<T>{}); }

  T x;
};

void init(int& i, tag<int>) { i = 42; }

Demo

Upvotes: 1

Richard Hodges
Richard Hodges

Reputation: 69912

You can solve the ordering problem by calling through a template function object - in the same way that boost::hash finds the ADL-function hash_value(x).

This works because the expansion of the template is deferred until the point of first use:

#include <iostream>

namespace A {

    struct XX {

        friend void init(XX&);
    };
}

namespace B {

    struct YY {
        friend void init(YY&);

    };
}

/// default case - call init on T found by ADL
template<class T>
struct call_init
{
    void operator()(T& o) const {
        init(o);
    }
};

template<typename T>
class Foo
{
public:
  Foo()
  {
    auto initialiser = call_init<decltype(this->x)>();
    initialiser(this->x);
  }

  T x;
};

void init(int& x) {
    x = 2;
}

// special case, initialise an int
template<> struct call_init<int>
{
    void operator()(int& x) const {
        init(x);
    }
};


int main()
{
  Foo<int> foo;
  Foo<A::XX> foox;
  Foo<B::YY> fooy;
  std::cout << foo.x << std::endl;
  return 0;
}

Upvotes: 2

Related Questions