Reputation: 4677
I am working maintenance on a program with a bunch of different structures that are fundamentally similar. I want to write a template method that utilizes SFINAE to enable calling this method from the client. When I define the template specialization inline, everything works as expected. However, when I try to move the template definition into a separate compilation unit, I run into issues. I am trying to move the template implementation into a separate file in order to enable using forward declarations for most of the dependent classes. The following is an example of what I am attempting to achieve:
// 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
I am using VS2017u2, and am getting error C2912: explicitt specialization 'int GetValue(const A &)' is not a specialization of a function template. Does anyone know how to make this work with the definitions in a separate compilation unit?
Upvotes: 1
Views: 343
Reputation: 40801
When you write enable_if_t<T::valueA, int>
, it is checking for a static member of T
called valueA
(Which would presumably be a static constexpr bool valueA = /* true or false */;
, like what T::value
would mean if T
was using T = std::is_same<U, V>;
).
To actually check if it has a member called valueA
or valueB
, put it in a context where there would be a substitution error if the member didn't exist, or true
. Something like:
// 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>
Even after fixing the SFINAE check, you are using the wrong syntax for template instantiation. You should use:
template int GetValue(A const &); // No `<>`
template int GetValue(B const &);
And after that, it still doesn't work because template<typename T> enable_if_t<..., int> GetValue(T const&);
and template<typename T> int GetValue(T const&);
are different functions, so it is ambiguous which one it should instantiate (as both would work). You need to make both of those into different functions (GetValueImpl2
in my example) and have GetValue
declared the same way as in the header:
#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 &);
Upvotes: 2