Q: Are there are any compile time mechanisms in C++ I can use to automatically verify that the set of template class methods matches from class specialization to specialization?
Example: Let us suppose I want a class interface that has very different behaviors depending on a template value specialization:
// forward declaration of template class name
template <typename T, bool isZero> class MyClass;
// partial class specialization for isZero = false case
template <typename T> class MyClass <T, false>
{
T _val;
public:
MyClass(T value) { _val = value; }
T GetValue(void) const { return _val; }
};
// partial class specialization for isZero = true case
template <typename T> class MyClass <T, true>
{
public:
MyClass(T value) {}
T GetValue(void) const {return T(0);}
};
The idea here is that when compiling on Tuesdays, MyClass.GetValue() returns 0, whereas any other day of the week, we get the expected value ...or some such thing. The details and motivations are unimportant. ;-)
Now, while this seems like one way to achieve partial function specialization based on partial class specialization, it also seems like it can also be utter chaos because the two class specializations can (AFAIK) have completely inconsistent interfaces. If I am actually going to use this mechanism in a production setting, I would like some ideally compile time notification if I mistakenly add some methods to some specializations and not others or forget a const somewhere, etc. How do I get that?
For bonus points, let us suppose I don't want to accidentally allow other values of isZero beyond
(true/false) to compile as might occur if I provided a generic implementation of
MyClass and let us also suppose I am suspicious of added runtime cost from adding virtual
here for pure virtual base class methods.
This seems like such an obvious language feature that I'm likely missing the forest for the trees here and maybe I'm already getting this behavior through some mechanism and haven't realized it yet.
> cat test.cpp
#include <stdio.h>
// forward declaration of template class name
template <typename T, bool isZero> class MyClass;
// partial specialization for isZero = false case
template <typename T> class MyClass <T, false>
{
T _val;
public:
MyClass(T value) { _val = value; }
T GetValue(void) const { return _val; }
};
// partial specialization for isZero = true case
template <typename T> class MyClass <T, true>
{
public:
MyClass(T value) {}
T GetValue(void) const {return T(0);}
};
int main( void )
{
MyClass<float, false> one(1);
MyClass<float, true> notOne(1);
printf( "%f, %f\n", one.GetValue(), notOne.GetValue());
return 0;
}
> clang -Wall -pedantic test.cpp
> ./a.out
1.000000, 0.000000
You could make a static assertion at the point of use:
template <typename T>
class MyOtherClass
{
static_assert(std::is_same_v<decltype(MyClass<T, true >{T{}}.GetValue()), T>);
static_assert(std::is_same_v<decltype(MyClass<T, false>{T{}}.GetValue()), T>);
...
};
You could also attempt to define a traits class that defines/identifies the interface, if you find yourself making this assertion in several places.
Because you're leaving a template parameter unspecialized (namely T), the traits are a little awkward, but these might work:
// This traits class will inherit from either
// std::true_type or std::false_type.
template <template <typename, bool> S, typename T>
struct is_value_provider : std::intergral_constant<bool,
std::is_same_v<decltype(S<T, true >{T{}}.getValue()), T> &&
std::is_same_v<decltype(S<T, false>{T{}}.getValue()), T>>
{}
template <template <typename, bool> S, typename T>
using is_value_provider_v = is_value_provider::value;
// Usage examples:
static_assert(is_value_provider_v<MyClass, int>);
static_assert(is_value_provider_v<MyClass, float>);
static_assert(is_value_provider_v<MyClass, double>);
Unit tests perhaps?
With Catch2, you could use TEMPLATE_TEST_CASE_SIG to compile the same test case for different specializations. You could express the similarities between specializations using "actual code" instead of traits.
It might look something like this:
TEMPLATE_TEST_CASE_SIG(
"MyClass specialization compat",
"[myclass]",
((typename T, bool isZero), T, isZero),
(int, false), (int, true)
) {
const MyClass<T, isZero> mc{T{}};
T val{mc.GetValue()};
}
This will ensure that GetValue exists, is marked const and returns something that can be narrowed to T. You don't have to use a unit testing framework. You can just use a function template. You don't need to actually run the tests. You just need to compile them.
template <typename T, bool isZero>
void test() {
const MyClass<T, isZero> mc{T{}};
T val = mc.GetValue();
}
inline void runTests() {
test<int, false>();
test<int, true>();
}
If you want to be more thorough, you end up with the same tedium as in Nicholas's answer.
Related
I have a family of classes in a library that can be "installed" in another class, either as a single member or as an array, dependent on the application. The arrays are indexed with an integer or enum type, dependent on the application (void is used when an array is not meaningful). The installable class itself has no control over the indexing; the application using the class defines the index.
However, I imagine that unwanted specialziations could be created by a typo and compile OK.
I want to constrain the indexing types to only the ones intended for the application, by making the client signal back to the library which associations are OK. I couldn't see a pure template metaprogramming approach, so I thought I'd exploit ODR and explicit specialization of class members.
namespace foo {
template <class P, class ROLE>
struct association
{
static_assert(std::is_enum_v<ROLE>||std::is_integral_v<ROLE>);
static const bool allowed();
};
template <class T>
class bar final
{
public:
bar() = default;
~bar() = default;
};
void do_something() {}
template <class I, class ROLE>
void install(I &&i, ROLE r)
{
if (association<std::decay_t<I>, ROLE>::allowed()) do_something();
}
template <class I>
void install(I &&i)
{
if (association<std::decay_t<I>, void>::allowed()) do_something();
}
}
With the following sample use:
// declare the indexing type
enum myindex { min=0, max=3 };
int main() {
foo::bar<int> foobar;
foo::install(foobar, myindex::min);
return 0;
}
There should be a linker error unless we also add
// add a definiton to make the association with myindex OK
template <> const bool foo::association<bar<int>, myindex>::allowed() { return true; }
In the full code, the value of "allowed" doesn't matter, only the existence of a definition does.
It's a pretty cryptic way of saying "this association is OK", but it works. At least, if you fully specialize association. But this is where the "sometimes" comes in: Some of the templates are supposed to work with any indexing type. It's a pain to make the library user write out specializations for these templates. But the following
template <class T, class ROLE> const bool foo::association<foo::bar<T>, ROLE>::allowed () { return true; }
is a compiler error, because it's not a full specialization.
Is there a way to fully define association::allowed() for all combinations of bar specializations with any ROLE, but force the user to define it for other templates?
If not, is there a better approach that accomplishes this goal? (Hopefull, something that can be used with static_assert because what I have now is charitably called 'clunky').
Remember, myindex cannot be rolled into the library. (I'm sticking to C++17 for the time being).
This case seems like a good place for template variables. One of their use is to make them a kind of a map - which in this case would greatly increase readability of the client code and move everything to compile-time. If you just want to break compilation the following should be fine:
#include <iostream>
#include <type_traits>
// make_association is a variable template which works as a compile-time map
// default value is false_type = no relation
template <typename I,typename ROLE>
constexpr std::false_type make_association;
// isAssociated used as a compile-time "function"
template <typename I, typename R>
using isAssociated = decltype(make_association<std::decay_t<I>,std::decay_t<R>>);
template <typename I, typename ROLE>
void install( I &&i, ROLE r)
{
static_assert(isAssociated<I,ROLE>(), "make_association<I, ROLE> not registered");
static_assert(std::is_enum_v<ROLE> || std::is_integral_v<ROLE> );
std::cout<<"Entered install( I &&, ROLE r)"<<std::endl;
}
template <typename I>
void install( I &&i)
{
static_assert(isAssociated<I,void>(), "make_association<I, void> not registered");
std::cout<<"Entered install( I &&)"<<std::endl;
}
enum WrongIndexT { ok = 1};
enum IndexT { min=0, max=3 };
class ObjT final {};
// a class for which any index works
template <class T> class Any final {};
template <class T, class Index>
constexpr std::true_type make_association<Any<T>, Index>;
// here the relations are set using variable template specialization;
template<> constexpr std::true_type make_association<ObjT, IndexT>;
template<> constexpr std::true_type make_association<ObjT, void>;
int main() {
ObjT f1,f2,f3;
install(f1, IndexT::min);
install(f2);
install(Any<int>{}, WrongIndexT::ok); // OK
install(f1, WrongIndexT::ok); // compilation error
return 0;
}
I write a template class dependent on a given type and variadic types, like so:
template<typename ConstType,typename...Inputs>
class ConstantTensor;
Then I write another class, which is generally defined in this way (assume wrong_type whatever type you want, but which is different from the following specialization ):
template<typename T>
class X{
public:
using type=wrong_type;
}
And I also have a specialization of this kind:
template<typename ConstType,typename...Inputs>
class X< ConstantTensor< ConstType ,Inputs...>>
{
public:
using type=right_type;
}
My problem is that, if I define the type ConstantTensor<ConstType,double> and then I want to use X<ConstantTensor<ConstType,double>>::type, the general case is called and not the specialization. So I obtain wrong_type instead of right_type. I guess it has to deal with the double type...Could you explain me why and how can I solve this issue? Thank you in advance.
EDIT:
Here a snippet of code, I hope it works:
class Scalar
{};
template<typename ConstType,typename...Inputs>
class ConstantTensor
{
public:
constexpr ConstantTensor(const Inputs&...inputs)
{}
};
template<typename ConstType,typename...Inputs>
constexpr auto Constant(const Inputs&...inputs)
{return ConstantTensor<ConstType,Inputs...>(inputs...);}
template<typename T>
class X{
public:
using type=int;
};
template<typename ConstType,typename...Inputs>
class X<ConstantTensor<ConstType,Inputs...>>{
public:
using type=char;
};
int main()
{
constexpr auto delta=Constant<Scalar>(2.0);
using type= X<decltype(delta)>::type; // this is int not char
}
The problem is that
constexpr auto delta=Constant<Scalar>(2.0);
is a constexpr variable; so it's also const.
So decltype(delta) isn't ConstantTensor<Scalar> but is a ConstantTensor<Scalar> const.
You can verify adding const in partial specialization declaration
template<typename ConstType,typename...Inputs>
class X<ConstantTensor<ConstType,Inputs...> const>{ // <-- added const
public:
using type=char;
};
Now you get that type is char.
-- EDIT --
The OP asks
Is there a short/elegant way to deal with both cases, const and non const, without duplicating the code?
I don't know if it's elegant, but it seems to me short enough: you can use a sort of self-inheritance adding the following partial specialization.
template <typename T>
class X<T const> : public X<T>
{ };
So X<ConstantTensor<Scalar> const> inherit from X<ConstantTensor<Scalar>>.
enum class enabler{};
template<typename T>
class X {
template<typename std::enable_if<std::is_class<T>::value,enabler>::type = enabler()>
void func();
void func(int a);
void func(std::string b);
};
I have this class with these 3 overloads for func. I need the second/third versions to be available for both class/non-class types, and the first version to be available only for class types. when I tried to use enable_if as above, the class instantiation for non-class types gives compile error.
For SFINAE to work, the template argument must be deduced. In your case, T is already known by the time you attempt to instantiate func, so if the enable_if condition is false, instead of SFINAE, you get a hard error.
To fix the error, just add a template parameter whose default value is T, and use this new parameter in the enable_if check. Now deduction occurs and SFINAE can kick in for non-class types.
template<typename U = T,
typename std::enable_if<std::is_class<U>::value,enabler>::type = enabler()>
void func();
And you don't really need a dedicated enabler type either, this works too
template<typename U = T,
typename std::enable_if<std::is_class<U>::value, int>::type* = nullptr>
void func();
I'm not really sure what you're going for with enabler here, but you can't do what you're trying because the declaration for your member function must be valid since T is not deduced by func. To achieve what you want in adding an extra overload, you can use some moderately contrived inheritance.
struct XBaseImpl {
// whatever you want in both versions
void func(int a) { }
void func(std::string b) { }
};
template <typename, bool> struct XBase;
// is_class is true, contains the extra overload you want
template <typename T>
struct XBase<T, true> : XBaseImpl {
static_assert(std::is_class<T>{}, ""); // just to be safe
using XBaseImpl::func;
void func() { } // class-only
};
// is_class is false
template <typename T>
struct XBase<T, false> : XBaseImpl { };
template<typename T>
class X : public XBase<T, std::is_class<T>{}> { };
You are not enabling or disabling something.
You simply want a compile time error in one specific case.
Because of that you don't require to rely on sfinae, a static_assert is enough.
As a minimal, working example:
#include<string>
template<typename T>
class X {
public:
void func() {
static_assert(std::is_class<T>::value, "!");
// do whatever you want here
}
void func(int a) {}
void func(std::string b) {}
};
int main() {
X<int> x1;
X<std::string> x2;
x2.func(42);
x2.func();
x1.func(42);
// compilation error
// x1.func();
}
Once a SO user said me: this is not sfinae, this is - substitution failure is always an error - and in this case you should use a static_assert instead.
He was right, as shown in the above example a static_assert is easier to write and to understand than sfinae and does its work as well.
I'm starting to learn about traits and templates in c++. What I'm wondering is is it possible to create templates for signed/unsigned integral types. The idea is that the normal class would (probably) be implemented for singed integer types, and the variation for unsigned integer types. I tried:
template <typename T>
class FXP<T>
{ ... };
template <typename T>
class FXP<unsigned T>
{ ... };
but this does not compile.
I even came across:
std::is_integral
std::is_signed
std::is_unsigned
So, how do I put these in action to define a class that only supports these two variants?
In this case, there's a few ways to go about it, but my favorite for cases where there's a limited number of variants (e.g., a boolean or two saying which way it should behave), partial specialization of a template is usually the best way to go:
// Original implementation with default boolean for the variation type
template <typename T, bool is_unsigned = std::is_unsigned<T>::value>
class FXP {
// default implementation here
};
Your next step is to then provide a partial specialization that takes the typename T, but only works for a specific variant of the template parameters (e.g. true or false).
template <typename T>
class FXP<T, false> {
// partial specialization when is_unsigned becomes false
};
template <typename T>
class FXP<T, true> {
// partial specialization when is_unsigned becomes true
};
In this case, if you write a default implementation, you only need to make a specialization for the case that's non-default (like the true case).
Here's an example, where the default case gets overridden by a specialized template parameter:
http://coliru.stacked-crooked.com/a/bc761b7b44b0d452
Note that this is better only for smaller cases. If you need complex tests, you're better off using std::enable_if and some more complicated template parameters (like in DyP's answer).
Good luck!
With an additional template parameter:
#include <iostream>
#include <type_traits>
template <typename T, class X = void>
struct FXP
{
// possibly disallow using this primary template:
// static_assert(not std::is_same<X, X>{},
// "Error: type neither signed nor unsigned");
void print() { std::cout << "non-specialized\n"; }
};
template <typename T>
struct FXP< T, typename std::enable_if<std::is_signed<T>{}>::type >
{ void print() { std::cout << "signed\n"; } };
template <typename T>
struct FXP< T, typename std::enable_if<std::is_unsigned<T>{}>::type >
{ void print() { std::cout << "unsigned\n"; } };
struct foo {};
int main()
{
FXP<foo>().print();
FXP<int>().print();
FXP<unsigned int>().print();
}
I'm working with some generated classes with broken polymorphism. For every generated class T, there are a handful of T_type_info, T_writer, T_reader classes which are only related to T conceptually.
What I'm trying to do is something like this:
template <class T> class Wrapper
{
public:
template <class W> W topic_cast(BrokenBaseClassWriter* p);
// other operations with the same problem ...
};
template <> class Wrapper<MyTopic>
{
public:
template <> MyTopicWriter* topic_cast(BrokenBaseClassWriter* p) { ... }
};
So that I can do things like:
void Write(const Wrapper<T>& topic)
{
BrokenBaseClassWriter p = not_important;
topic.topic_cast(p)->do_stuff();
}
My T classes are generated from an IDL and are concepts that exist in application space. They don't derive from anything. In my example above, W is not really an independent parameter, it's "Something Not T that depends on T". I'm trying to keep all knowledge of T in the app, and all knowledge of T' (without knowing about T) in the backend.
The compiler however says my topic_cast function is not a template function - I think because the template occurs in the return type and it wouldn't be distinguishable from any other instantiations. I know that that (templates differ only by return type) is not legal. Only in my case it really would be unique because W is not an independent parameter. But arguing with the compiler is seldom helpful.
Can I do this, or is there another way to do this "cast as function of template type"?
Could this not be achieved with a traits system?
template <typename T> struct my_traits
{
};
template <> struct my_traits<MyClass>
{
typedef MyWriter writer_type;
};
template <typename T> struct Wrapper
{
typename my_traits<T>::writer_type topic_cast();
};
This can't work:
topic.topic_cast(p)->do_stuff();
Because the compiler can not deduce the return type.
So you have to explicitly tell the compiler what return type you want:
topic.topic_cast<MyType>(p)->do_stuff();
The implementation you provide is for a specific type.
So when you use that specific type that code will be produced:
topic.topic_cast<MyTopicWriter>(p)->do_stuff(); // Use the explicit specialization
This compiles with gcc:
class BrokenBaseClassWriter;
class MyTopic;
class MyTopicWriter;
template <class T> class Wrapper
{
public:
template <class W> W *topic_cast(BrokenBaseClassWriter* p);
// other operations with the same problem ...
};
template <> template<>
MyTopicWriter *Wrapper<MyTopic>::topic_cast<MyTopicWriter>(BrokenBaseClassWriter* p)
{
return 0;
}
int main(int argc, int argv)
{
BrokenBaseClassWriter* p = NULL;
Wrapper<MyTopic> caster;
MyTopicWriter *casted = caster.topic_cast<MyTopicWriter>(p);
}
Of course, it still exposes MyTopicWriter in your main code...