Включить шаблон только для определенного шаблонного класса

Этот вопрос вдохновлен моим предыдущим вопросом Без вывода параметров шаблона из пакета параметров.

Рассмотрим следующий пример кода:

#include <memory>
#include <string>

template<typename... FArgs>
class Callback
{
    public:
    class Handle{};
};

class BaseCallbackHandle
{
};

using TypeErasedCallbackHandle = std::unique_ptr<BaseCallbackHandle>;

template<typename H>
TypeErasedCallbackHandle makeTypeErasedCallbackHandle( H handle)
{
   return {};
}

int main()
{
    Callback<int>::Handle h;
    std::string s;
    makeTypeErasedCallbackHandle(h); //should compile fine
    makeTypeErasedCallbackHandle(s); //should raise a compile error
}

См. также http://coliru.stacked-crooked.com/a/5f2a2e816eef6afd.

Шаблон функции makeTypeErasedCallbackHandle теперь принимает любой класс в качестве входного параметра. Есть ли способ гарантировать (например, с помощью static-assert или enable_if), что только Callback<FArgs...>::Handle (с любым FArgs) разрешено как H? Пример с Callback<int>::Handle должен скомпилироваться, а std::string не получится.


person meddle0106    schedule 17.06.2016    source источник
comment
Возможный дубликат Выполнение static_assert, что тип шаблона это другой шаблон   -  person m.s.    schedule 17.06.2016


Ответы (4)


Определите тип в своем классе Handle и обратитесь к этому типу внутри класса makeTypeErasedCallbackHandle():

#include <memory>
#include <string>

template <typename... FArgs>
struct Callback {
    struct Handle {
        using callback_type = Callback<FArgs...>;    
    };
};

struct BaseCallbackHandle {
};

using TypeErasedCallbackHandle = std::unique_ptr<BaseCallbackHandle>;

template <typename H>
TypeErasedCallbackHandle makeTypeErasedCallbackHandle(H handle) {
    using callback_type = typename H::callback_type;

    return {};
}

int main() {
    Callback<int>::Handle h;
    std::string s;
    makeTypeErasedCallbackHandle(h); //should compile fine
    makeTypeErasedCallbackHandle(s); //should raise a compile error
}

Живой пример

Это не удастся во время создания экземпляра для любого H, который не определяет вложенный тип.


Приложив немного больше усилий, вы сможете static_assert создать осмысленное сообщение для клиента, в то же время повысив гибкость решения с помощью признаков типа. Это имеет то преимущество, что callback_impl::is_callback может быть специализировано для произвольных типов дескрипторов:

#include <memory>
#include <string>

namespace callback_impl {

struct callback_identification_type {};

template <typename T, typename = void>
struct is_callback : std::false_type {};

template <typename T>
struct is_callback<T,
    std::enable_if_t<std::is_same<typename T::callback_id_type,
                     callback_identification_type>::value>>
 : std::true_type {};

}

template <typename... FArgs>
struct Callback {
    struct Handle {
        using callback_id_type = callback_impl::callback_identification_type;    
    };
};

struct BaseCallbackHandle {
};

using TypeErasedCallbackHandle = std::unique_ptr<BaseCallbackHandle>;

template <typename H>
TypeErasedCallbackHandle makeTypeErasedCallbackHandle(H handle) {
    static_assert(callback_impl::is_callback<H>::value,
                  "The handle type is not a member of a recognised Callback<T...>");
    return {};
}

int main() {
    Callback<int>::Handle h;
    std::string s;
    makeTypeErasedCallbackHandle(h); //should compile fine
    makeTypeErasedCallbackHandle(s); //should raise a compile error

    return 0;
}

Живой пример

Выход:

g++ -std=c++14 -O2 -Wall -Wno-unused-local-typedefs -pedantic -pthread main.cpp && ./a.out
main.cpp: In instantiation of 'TypeErasedCallbackHandle makeTypeErasedCallbackHandle(H) [with H = std::__cxx11::basic_string<char>; TypeErasedCallbackHandle = std::unique_ptr<BaseCallbackHandle>]':
main.cpp:41:35:   required from here
main.cpp:32:5: error: static assertion failed: The handle type is not a member of a recognised Callback<T...>
     static_assert(callback_impl::is_callback<H>::value,
     ^~~~~~~~~~~~~
person Andrew    schedule 17.06.2016

Один из способов сделать это — передать несколько дополнительных аргументов:

template <typename... Pack> struct packer {};

using TypeErasedCallbackHandle = std::unique_ptr<BaseCallbackHandle>;

template <typename... T>
TypeErasedCallbackHandle makeTypeErasedCallbackHandle(typename Callback<T...>::Handle h, T...)
{
   return {};
}

template <typename... T>
TypeErasedCallbackHandle makeTypeErasedCallbackHandle_2(typename Callback<T...>::Handle h, packer<T...>)
{
   return {};
}

int main()
{
    Callback<int>::Handle h;
    std::string s;
    makeTypeErasedCallbackHandle(h, 0); //should compile fine
    // OR
    makeTypeErasedCallbackHandle_2(h, packer<int>());

    //makeTypeErasedCallbackHandle(s); //should raise a compile error
}

Это использует identity trick (от Stephan T. Lavavej) для вывода типа.

person Arunmu    schedule 17.06.2016

Вы можете создать фиктивный элемент для своего семейства классов и вызвать его на стороне подстановки: http://coliru.stacked-crooked.com/a/d5738766fd7ac45f

class Handle
{
    public:
    static void doNothing(){}
};

...

TypeErasedCallbackHandle makeTypeErasedCallbackHandle( H handle)
{
   H::doNothing();
   return {};

Более стандартный способ - использовать черты класса или статические утверждения из таких библиотек, как BOOST (возможно, некоторые из них уже являются частью языка).

РЕДАКТИРОВАТЬ: http://coliru.stacked-crooked.com/a/2a3adcb9d9dd274c таким образом чуть лучше, var вместо call.

person Alexey Birukov    schedule 17.06.2016

class BaseCallback
{
  ~BaseCallback() = 0;
};

template<typename... FArgs>
class Callback : public BaseCallback
{
  ~Callback(){...}
  ...
};

template<typename H>
TypeErasedCallbackHandle makeTypeErasedCallbackHandle( H handle)
{
  static_assert(std::is_base_of<BaseCallback, H>::value, "Must use a callback");
  return {};
}

Это должно работать и разрешать вам использовать эту функцию только для обратных вызовов.

person Altainia    schedule 17.06.2016