Templating a double-templated class method

97 views Asked by At

This is my simplified code :

template<typename device, template<typename device> class protocol>
class MyClass
{
public:
  template<typename select>
  bool method()
  {
    // Code
  }
};

I want method to act different in terms of the protocol type.

In other words, I have two different possible protocols, and I want to have two different behaviours for my method according to the protocols. But I don't know how to write it with templates.

4

There are 4 answers

8
max66 On BEST ANSWER

By example, using SFINAE (if you accept a C++11 solution)

#include <iostream>
#include <type_traits>

template <typename>
struct protocol_1
 { };

template <typename>
struct protocol_2
 { };

template<typename device, template<typename> class protocol>
class MyClass
 {
   public:
      template<typename select, typename p = protocol<device>>
         typename std::enable_if<
         std::is_same<p, protocol_1<device>>::value, bool>::type
         method()
       { return true; }

      template<typename select, typename p = protocol<device>>
         typename std::enable_if<
         std::is_same<p, protocol_2<device>>::value, bool>::type
         method()
       { return false; }
 };

int main()
 {
   MyClass<int, protocol_1>  m1;
   MyClass<int, protocol_2>  m2;

   std::cout << m1.method<int>() << std::endl; // print 1 (true)
   std::cout << m2.method<void>() << std::endl; // print 0 (false)
 }

--- EDIT ---

As pointed by Yakk (thanks!), this solution is weak because use a template default value that can be explicited and circunvented.

An example; with

MyClass<int, protocol_1>{}.method<void>(); 

is called the "protocol_1" version of method(), using the default value for p; but explciting p, as follows

MyClass<int, protocol_1>{}.method<void, protocol_2<int>>(); 

is called the "protocol_2" version of method() over an istance of a MyClass based on protocol_1

To avoid this problem it's possible add a static_assert(), in both version of method(), to check and impose that p is equal to its default value (protocol<device>)

I mean... as follow

  template<typename select, typename p = protocol<device>>
     typename std::enable_if<
        std::is_same<p, protocol_1<device>>::value, bool>::type
     method()
   {
     static_assert(std::is_same<p, protocol<device>>::value, "!");

     return true;
   }

  template<typename select, typename p = protocol<device>>
     typename std::enable_if<
     std::is_same<p, protocol_2<device>>::value, bool>::type
     method()
   {
     static_assert(std::is_same<p, protocol<device>>::value, "!");

     return false;
   }

So

MyClass<int, protocol_1>{}.method<void, protocol_2<int>>();

generate a compiler error.

0
Danh On

Use an external helper is another possible solution:

template <typename T>
struct B {
};
template <template<typename> class protocol, typename select>
struct helper {
    static bool do_real() {
        return true;
    }
};
template <typename select>
struct helper<B, select> {
    static bool do_real() {
        return false;
    }
};
template<typename device, template<typename> class protocol>
class MyClass
{
public:
  template<typename select>
  bool method()
  {
    return helper<protocol, select>::do_real();
  }
};

Like this

0
Jarod42 On

You may partial specialize the class, something like:

template<typename device, template<typename> class protocol>
class MyClass;

template <typename> struct protocole1 { /* ... */ };
template <typename> struct protocole2 { /* ... */ };

template<typename device>
class MyClass<device, protocol1>
{
public:
  template<typename select>
  bool method()
  {
    // Code for protocole1
  }
};

template<typename device>
class MyClass<device, protocol2>
{
public:
  template<typename select>
  bool method()
  {
    // Code for protocole2
  }
};
0
skypjack On

You didn't mention which is the target standard, so I'll try to give you an alternative for all the seasons.


In C++11/14 you can use tag dispatching and function overloading to do that.
It follows a minimal, working example:

#include<iostream>

template<typename> struct protocol_a {};
template<typename> struct protocol_b {};

template<typename device, template<typename> class protocol>
class MyClass {
    template<template<typename> class> struct tag {};

    template<typename select>
    bool method(tag<protocol_a>) {
        std::cout << "protocol_a" << std::endl;
        return true;
    }

    template<typename select>
    bool method(tag<protocol_b>) {
        std::cout << "protocol_b" << std::endl;
        return false;
    }

public:
    template<typename select>
    bool method() {
        return method<device>(tag<protocol>{});
    }
};

int main() {
    MyClass<int, protocol_a> mca;
    mca.method<void>();
    MyClass<int, protocol_b> mcb;
    mcb.method<void>();
}

Pretty compact. It doesn't require extra classes, partial specializations or sfinae expressions. The drawback (if we can call this a drawback) is that you have one more level of indirection.


In C++17 you can use if constexpr to get the same result.
It follows a minimal, working example:

#include<type_traits>
#include<iostream>

template<typename> struct protocol_a {};
template<typename> struct protocol_b {};

template<typename device, template<typename> class protocol>
class MyClass {
public:
    template<typename select>
    bool method() {
        if constexpr(std::is_same_v<protocol<device>, protocol_a<device>>) {
            std::cout << "protocol_a" << std::endl;
            return true;
        } else if constexpr(std::is_same_v<protocol<device>, protocol_b<device>>) {
            std::cout << "protocol_b" << std::endl;
            return false;
        }
    }
};

int main() {
    MyClass<int, protocol_a> mca;
    mca.method<void>();
    MyClass<int, protocol_b> mcb;
    mcb.method<void>();
}

More compact, but C++17 could not be an option.
See it running on wandbox.