简体   繁体   中英

Template specialization as a function pointer

Consider the following code:

typedef void(__cdecl *logging_fnc)(int, const char*, ...);

template<logging_fnc LogFnc>
class Logger {
public:
  void foo()
  {
    LogFnc(1, "Foo");
  }

};

template<class Target, Target& instance>
void MakeLogAtTarget(int lvl, const char* msg...)
{
   instance.log("specific foo", lvl, msg);
}

The Logger somewhere simply calls the non-type template LogFnc from some method.

Now I also have a singleton class MyTarget .

class MyTarget {
public:
  void log(const char* targetSpecific, int lvl, const char* msg)
  {
    printf("%s %s", targetSpecific, msg);
  }

  static MyTarget& GetInstance()
  {
    static MyTarget myInstance;
    return myInstance;
  }

private:
  MyTarget() = default;
};

Why exactly the following doesn't work?

using TargetLogger = Logger<MakeLogAtTarget<MyTarget, MyTarget::GetInstance()>>;

I keep getting: cannot convert from 'void (__cdecl *)(int,const char *,...)' to 'logging_fnc'

Is it simply because the compiler cannot get the proper address of the specialization of MakeLogAtTarget it generates?

Generally speaking: is it possible to feed the non-template argument a pointer to a function which in turn invokes a method from some class instance?

UPDATE : Also tried

static const MyTarget targetInstance = MyTarget::GetInstance();
using TargetLogger = Logger<MakeLogAtTarget<MyTarget, targetInstance)>>;

Still the same error.

UPDATE

static const MyTarget targetInstance = MyTarget::GetInstance();
static const logging_fnc targetFnc = MakeLogAtTarget<MyTarget, targetInstance>;
using TargetLogger = Logger<targetFnc>;

outputs the same error at the line where targetFnc is declared, moreover an additional error appears at the last line: 'targetFnc': an expression involving objects with internal linkage cannot be used as a non-type argument

The issue is you're failing to meet the requirements for the template non-type argument of type Target& . From [temp.arg.nontype]:

A template-argument for a non-type template-parameter shall be a converted constant expression (5.20) of the type of the template-parameter .

Where a converted constant expression prohibits, from [expr.const]:

A conditional-expression e is a core constant expression unless the evaluation of e , following the rules of the abstract machine (1.9), would evaluate one of the following expressions: [...] an invocation of a function other than a constexpr constructor for a literal class, a constexpr function, or an implicit invocation of a trivial destructor (12.4)

You'll need some specific MyTarget object to refer to (or, in a post- N4198 world, a constexpr function that returns such a thing or something similar). Like:

struct MyTarget { ... };
static MyTarget x;

using TargetLogger = Logger<MakeLogAtTarget<MyTarget,x>>;

The problem can probably be reduced to a simple

template <typename T, T& t> void bar() {}

struct Z
{
  static constexpr Z& get();
};

static Z z;

constexpr Z& Z::get() { return z; }

int main()
{
    bar<Z, Z::get()>();
}

In C++11 and C++14 modes GCC responds with

main.cpp: In function 'int main()':
main.cpp:14:22: error: no matching function for call to 'bar()'
     bar<Z, Z::get()>();
                      ^
main.cpp:1:34: note: candidate: template<class T, T& t> void bar()
 template <typename T, T& t> void bar() {}
                                  ^~~
main.cpp:1:34: note:   template argument deduction/substitution failed:
main.cpp:14:22: error: 'Z::get()' is not a valid template argument for type 'Z&' because it is not an object with linkage
     bar<Z, Z::get()>();
                      ^

It does not like your attempt to use a function return as an argument for a reference-typed template parameter.

Once the code becomes more complicated (like yours) the descriptive error messages disappear, replaced with the message you quoted or a very similar one.

Yet, it compiles fine in -std=c++17 mode.

It's not clear what you are trying to achieve. It seems that you have a singleton instance that you want to feed to a logger? I'm missing the bigger picture.

template<class Target, Target& instance>

Here it seems you want to use an instance of an object as a template parameter. However, afaik templates are a compile-time construct, while the instance is generated at run-time. This construction would be impossible in that case. But I'm no expert.

edit: hmm, a -1 feedback modifier again. The guys/gal doing that should comment why.

edit 2: So now I understand your problem better. But then again the question: is it required to have MakeLogAtTarget fully template-able?

What you can do is use function pointers as template arguments. Eg:

template<class Target, Target& (*Getter)()>
void MakeLogAtTarget(int lvl, const char* msg...)
{
    Getter().log("specific foo", lvl, msg);
}

Thus you can call it using:

using TargetLogger = Logger < MakeLogAtTarget < MyTarget, MyTarget::GetInstance >> ;

Is that what you are looking for?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM