Home > database >  Implementing template specialized functions
Implementing template specialized functions

Time:01-20

I'm trying to allow a class to implement an interface, where the interface is templated to either allow an update internally or not. The code looks like this and works if the implementation is within the class declaration. If it is moved out (as shown here), I get a compiler error on Visual Studio 2019, and I can't understand why. All help is appreciated!

The following code will not compile. If the out of class implementation and headers are modified to just use the code I commented out, it compiles and works as expected.

#include <atomic>
#include <cstdint>
#include <memory>
#include <iostream>
#include <string>


enum class EntryType { Read, ReadUpdate };

template <EntryType Type>
class IXQ
{
public:
    virtual float GetValue() = 0;
};

class X : public IXQ<EntryType::Read>, public IXQ<EntryType::ReadUpdate>
{
public:
    float IXQ<EntryType::Read>::GetValue();
    /*
    float IXQ<EntryType::Read>::GetValue()
    {
        return _x;
    }
    */
    float IXQ<EntryType::ReadUpdate>::GetValue();
    /*
    float IXQ<EntryType::ReadUpdate>::GetValue() {
        _counter  ;
        return _x;
    }
    */
    float _x = 10.0F;
    std::atomic<std::int32_t> _counter = 0;
};

float X::IXQ<EntryType::Read>::GetValue()
{
    return _x;
}

float X::IXQ<EntryType::ReadUpdate>::GetValue()
{
    _counter  ;
    return _x;
};


int main(int argc, char* argv[])
{
    {
        auto k = std::make_unique<X>();

        auto ptrQ = static_cast<IXQ<EntryType::Read>*>(k.get());

        std::cout << std::to_string(ptrQ->GetValue()) << std::endl;

        std::cout << k->_counter.load() << std::endl;
    }

    {
        auto k = std::make_unique<X>();

        auto ptrQ = static_cast<IXQ<EntryType::ReadUpdate>*>(k.get());

        std::cout << std::to_string(ptrQ->GetValue()) << std::endl;

        std::cout << k->_counter.load() << std::endl;
    }


    return 0;
}

CodePudding user response:

1. Microsoft-specific syntax

The syntax you're using to override GetValue() is not standard c .

class X : public IXQ<EntryType::Read> {
public:
    float IXQ<EntryType::Read>::GetValue() { /* ... */ }
}

This is a Microsoft-Specific Extension for C called Explicit Overrides (C ) and is intended to be used with __interfaces (also a microsoft-specific extension to c ).

__interfaces do not officially support c templates due to them being intended mostly for COM Interfaces, e.g.:

[ object, uuid("00000000-0000-0000-0000-000000000001"), library_block ]
__interface ISomething {
   // properties
   [ id(0) ] int iInt;
   [ id(5) ] BSTR bStr;

   // functions
   void DoSomething();
};

It's suprising that float IXQ<EntryType::Read>::GetValue() { /* ... */ } works at all.


2. Standard C -compliant solutions

In standard C the overriden function is determined by the name & arguments alone, so your only option is to override both of them.

e.g.:

class X : public IXQ<EntryType::Read>, public IXQ<EntryType::ReadUpdate>
{
public:
    // will be the implementation for both IXQ<EntryType::Read> & IXQ<EntryType::ReadUpdate>
    float GetValue() override;
};

float X::GetValue() {
    /* ... */
}

Another standard-compliant way would be to create 2 classes that inherit from the given interfaces & then let X inherit from both of them:

class XRead : public IXQ<EntryType::Read> {
public:
    float GetValue() override { /* ... */ }
};

class XReadUpdate : public IXQ<EntryType::ReadUpdate> {
public:
    float GetValue() override { /* ... */ }
};

class X : public XRead, public XReadUpdate {
    /* ... */
};

If you want to share state between XRead & XReadUpdate, you'd have to introduce another level, e.g.:

class XBase {
public:
    virtual ~XBase() = default;
protected:
    float value;
};

class XRead : public virtual XBase, public IXQ<EntryType::Read> {
    float GetValue() override { return value; }
};

class XReadUpdate : public virtual XBase, public IXQ<EntryType::ReadUpdate> {
    float GetValue() override { return value; }
};

class X : public XRead, public XReadUpdate {
    /* ... */
};

3. Possible recommendations for API Design

Keep in mind though that this type of API design will be rather complicated to use, because you always need to cast to the specific interface first before you can call the function because X{}.GetValue() would be ambigous.

e.g.:

X x;

IXQ<EntryType::Read>& read = x;
std::cout << read.GetValue() << std::endl;

IXQ<EntryType::ReadUpdate>& update = x;
std::cout << update.GetValue() << std::endl;

// this is *not* possible, GetValue() is ambigous
// std::cout << x.GetValue() << std::endl;

I'd recommend separating both interfaces & using different method names, e.g.: godbolt example

struct IXQReadonly {
    virtual ~IXQReadonly() = default;
    virtual float GetValue() = 0;
};

struct IXQ : IXQReadonly {
    virtual float GetValueUpdate() = 0;
};

class X : public IXQ {
public:
    X() : value(0.0f), counter(0) { }

    float GetValue() override { return value; }
    float GetValueUpdate() override {
          counter;
        return value;
    }

private:
    float value;
    std::atomic<int> counter;
};

This comes with a whole set of benefits:

  • You can call GetValue() / GetValueUpdate() directly on X, no need to convert to an interface first
    X x;
    x.GetValue();
    x.GetValueUpdate();
    
  • It is immediately clear from the method name if it will update the counter or not
  • A function that is given an IXQ can call a function that expects IXQReadonly, but not the other way round: (so you can "downgrade" a read-write interface to readonly, but not "upgrade" a readonly interface to read-write)
    void bar(IXQReadonly& r) { r.GetValue(); /* we can't do GetValueUpdate() here */ }
    void foo(IXQ& x) { bar(x); x.GetValueUpdate(); }
    

Additionally remember to declare the destructor as virtual (at least in the top-most class / interface), otherwise funny things tend to happen if you try to delete an instance of X through a pointer to one of its bases / interfaces.

CodePudding user response:

I think this can be solved by adding an intermediate layer of classes, let's say X_Read and X_ReadUpdate from which X can inherit.

      IXQ
     /   \
X_Read   X_ReadUpdate
     \   /
       X

The trick is using those classes to implement IXQ::GetValue by calling two virtual pure methods, let's say GetValueRead and GetValueReadUpdate respectively. This way, X still inherits from IXQ<EntryType::Read> and IXQ<EntryType::ReadUpdate>, but comes to implement GetValueRead and GetValueReadUpdate, two methods with clear and distinct interfaces.

[Demo]

template <EntryType Type>
class IXQ
{
public:
    virtual float GetValue() = 0;
};

class X_Read : public IXQ<EntryType::Read>
{
public:
    virtual float GetValue() override { return GetValueRead(); };
    virtual float GetValueRead() = 0;
};

class X_ReadUpdate : public IXQ<EntryType::ReadUpdate>
{
public:
    virtual float GetValue() override { return GetValueReadUpdate(); };
    virtual float GetValueReadUpdate() = 0;
};

class X : public X_Read, public X_ReadUpdate
{
public:
    virtual float GetValueRead() override { return _x; }
    virtual float GetValueReadUpdate() { _counter  ; return _x; };
    float _x{10.0f};
    std::atomic<std::int32_t> _counter{};
};

// Outputs:
//   10.000000
//   0
//   10.000000
//   1
  •  Tags:  
  • Related