Специализация шаблона SFINAE в отдельном исходном файле - PullRequest
1 голос
/ 27 мая 2019

Я работаю над обслуживанием программы с кучей различных структур, которые в основном похожи. Я хочу написать шаблонный метод, который использует SFINAE, чтобы разрешить вызов этого метода от клиента. Когда я определяю специализацию шаблона встроенным, все работает как положено. Однако, когда я пытаюсь переместить определение шаблона в отдельный модуль компиляции, у меня возникают проблемы. Я пытаюсь переместить реализацию шаблона в отдельный файл, чтобы включить использование предварительных объявлений для большинства зависимых классов. Ниже приведен пример того, чего я пытаюсь достичь:

// Impl.h
#pragma once

struct A {
    struct {
        int value;
    } valueA;
};

struct B {
    struct {
        int value;
    } valueB;
};

template<typename T>
int GetValue(T const &value);

// Impl.cpp
#include "Impl.h"
#include <type_traits>

using std::enable_if_t;
using std::remove_reference_t;

template<typename T, typename U, U(T::*Value)>
static inline int GetValueImpl(T const &value) {
    return (value.*Value).value;
}

template<typename T>
enable_if_t<T::valueA, int> GetValue(T const &value) {
    static constexpr auto T::*const Value = &T::valueA;
    typedef remove_reference_t<decltype(value.*Value)> ValueType;
    return GetValueImpl<T, ValueType, Value>(value);
}

template<typename T>
enable_if_t<T::valueB, int> GetValue(T const &value) {
    static constexpr auto T::*const Value = &T::valueB;
    typedef remove_reference_t<decltype(value.*Value)> ValueType;
    return GetValueImpl<T, ValueType, Value>(value);
}

template<> int GetValue(A const &); // C2912 here
template<> int GetValue(B const &); // C2912 here

Я использую VS2017u2 и получаю ошибку C2912: явная специализация 'int GetValue (const A &)' не является специализацией шаблона функции. Кто-нибудь знает, как заставить это работать с определениями в отдельном модуле компиляции?

1 Ответ

2 голосов
/ 27 мая 2019

Когда вы пишете enable_if_t<T::valueA, int>, он проверяет наличие статического члена T, называемого valueA (который предположительно будет static constexpr bool valueA = /* true or false */;, например, что T::value будет означать, если T было бы using T = std::is_same<U, V>; ).

Чтобы на самом деле проверить, есть ли у него элемент с именем valueA или valueB, поместите его в контекст, в котором произошла бы ошибка замещения, если элемент не существует, или true. Что-то вроде:

// Pointers to member variables can never be null
// so these will always be enabled if `valueA` or
// `valueB` exist in the first place
enable_if_t<&T::valueA != nullptr, int>
enable_if_t<&T::valueB != nullptr, int>

// But that also allows pointers to static members
// so if you don't want that, you can do something else.
// Like checking if `&T::member` is a pointer to a member
// (But this is probably overkill as you have a specific set
// of types and none of those names are ever static members
// and if you didn't there is a small caveat with
// an overloaded `operator&` but that doesn't really matter)
enable_if_t<std::is_same_v<decltype(&T::valueA), decltype(T::valueA) T::*>, int>
enable_if_t<std::is_same_v<decltype(&T::valueB), decltype(T::valueB) T::*>, int>

Даже после исправления проверки SFINAE вы используете неправильный синтаксис для создания экземпляра шаблона. Вы должны использовать:

template int GetValue(A const &);  // No `<>`
template int GetValue(B const &);

И после этого он по-прежнему не работает, потому что template<typename T> enable_if_t<..., int> GetValue(T const&); и template<typename T> int GetValue(T const&); - это разные функции, поэтому неясно, какую именно функцию следует создать (как работают оба). Вам нужно преобразовать оба этих в разные функции (GetValueImpl2 в моем примере) и объявить GetValue так же, как в заголовке:

#include <type_traits>
#include "Impl.h"

using std::enable_if_t;
using std::remove_reference_t;

template<typename T, typename U, U(T::*Value)>
static inline int GetValueImpl(T const &value) {
    return (value.*Value).value;
}

template<typename T>
enable_if_t<std::is_same_v<decltype(&T::valueA), decltype(T::valueA) T::*>, int> GetValueImpl2(T const &value) {
    static constexpr auto T::*const Value = &T::valueA;
    using ValueType = decltype(T::valueA);
    return GetValueImpl<T, ValueType, Value>(value);
}

template<typename T>
enable_if_t<std::is_same_v<decltype(&T::valueB), decltype(T::valueB) T::*>, int> GetValueImpl2(T const &value) {
    static constexpr auto T::*const Value = &T::valueB;
    using ValueType = decltype(T::valueB);
    return GetValueImpl<T, ValueType, Value>(value);
}

template<typename T>
int GetValue(T const&value) {
    return GetValueImpl2(value);
}


template int GetValue<A>(A const &);
template int GetValue<B>(B const &);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...