Unit testing highly templated library - c++

I was wondering if something as unit testing template was a thing. Let me explain my needs.
I have a highly templated library. I have a lot of sfinae type traits, and some static_assert.
What I want to test is the validity of sfinae type traits, and test if my static_assert are throwing the right thing. Knowing what is my coverage would be awesome.
Here's an example of what my code look like:
template<typename T>
using false_v = !std::is_same<T, T>::value;
// Here are my types traits
template<typename T, typename... Args>
struct SomeCondition1 { /* ... */ };
template<typename T, typename... Args>
struct SomeCondition2 { /* ... */ };
// This is a master type trait, that test every others
template<typename T, typename... Args>
using Conditions = std::integral_constant<bool,
SomeCondition1<T, Args...>::value && SomeCondition2<T, Args...>::value
>;
// This is the function that is call when everything is okay.
template<typename T, typename... Args,
std::enable_if_t<Conditions<T, Args...>::value, int> = 0>
void doThing(Args...) {}
// These function are called only to trigger
// static asserts to give the user a diagnostic to explain what's wrong.
template<typename T, typename... Args,
std::enable_if_t<SomeCondition1<T, Args...>::value && !SomeCondition2<T, Args...>::value, int> = 0>
void doThing(Args...) {
static_assert(false_v<T>, "Error, SomeCondition2 not met");
}
template<typename T, typename... Args,
std::enable_if_t<!SomeCondition1<T, Args...>::value && SomeCondition2<T, Args...>::value, int> = 0>
void doThing(Args...) {
static_assert(false_v<T>, "Error, SomeCondition1 not met");
}
template<typename T, typename... Args,
std::enable_if_t<!SomeCondition1<T, Args...>::value && !SomeCondition2<T, Args...>::value, int> = 0>
void doThing(Args...) {
static_assert(false_v<T>, "Error, both conditions not met");
}
I was thinking about testing if the traits were ok, and if the right static assert is thrown for my cases. If the wrong static assert is triggered, that's a bug, and I would like to be able to test it. Trying to cover all cases for all compilers and check every message by hand is really time consuming and error prone.

The problem of unit-testing template code for ranges of argument types is
fairly well addressed by googletest
with its TYPED TESTS feature
and Type-Parameterized Tests
feature.
A limitation of these features is that they are only immediately applicable to testing
templates with just one parameter. But it's not difficult to work around this limitation:
see this question and the accepted answer.
None of that helps, however, with the further problem of testing the correctness static_asserts in
template code. The special obstacle for that kind of testing, of course, is that
a static_assert fires by compilation failure; so if it fires, correctly or otherwise,
there is nothing you can execute to show that it does.
This bothered me too several years ago. I posted How to write runnable tests of static_assert? and also wrote the only answer so far
received (lately updated for C++14 and current compilers).
Combining these resources and techniques should yield the solution you're looking for.

Related

Get number of template parameters with template template function

I'm not sure if this is possible, but I would like to count the number of template arguments of any class like:
template <typename T>
class MyTemplateClass { ... };
template <typename T, typename U>
class MyTemplateClass2 { ... };
such that template_size<MyTemplateClass>() == 1 and template_size<MyTemplateClass2>() == 2. I'm a beginner to template templates, so I came up with this function which of course does not work:
template <template <typename... Ts> class T>
constexpr size_t template_size() {
return sizeof...(Ts);
}
because Ts can not be referenced. I also know that it might come to problems when handling variantic templates, but that is not the case, at least for my application.
Thx in advance
There is one...
° Introduction
Like #Yakk pointed out in his comment to my other answer (without saying it explicitly), it is not possible to 'count' the number of parameters declared by a template. It is, on the other hand, possible to 'count' the number of arguments passed to an instantiated template.
Like my other answer shows it, it is rather easy to count these arguments.
So...
If one cannot count parameters...
How would it be possible to instantiate a template without knowing the number of arguments this template is suppose to receive ???
Note
If you wonder why the word instantiate(d) has been stricken throughout this post,
you'll find its explanation in the footnote. So keep reading... ;)
° Searching Process
If one can manage somehow to try to instantiate a template with an increasing number of arguments, and then, detect when it fails using SFINAE (Substitution Failure Is Not An Error), it should be possible to find a generic solution to this problem... Don't you think ?
Obviously, if one wants to be able to also manage non-type parameters, it's dead.
But for templates having only typename parameters...
There is one...
Here are the elements with which one should be able to make it possible:
A template class declared with only typename parameters can receive any type as argument. Indeed, although there can have specializations defined for specific types,
a primary template cannot enforce the type of its arguments.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The above statement might no longer be true from C++20 concepts.
I cannot try ATM, but #Yakk seems rather confident on the subject. After his comment:
I think concepts breaks this. "a primary template cannot enforce the type of its arguments." is false.
He might be right if constraints are apply before the template instantiation. But...
By doing a quick jump to the introduction to Constraints and concepts, one can read, after the first code example:
"Violations of constraints are detected at compile time, early in the template instantiation process, which leads to easy to follow error messages."
To be confirmed...
It is perfectly possible to create a template having for sole purpose to
be instantiated with any number of arguments. For our use case here, it might contain only ints... (let's call it IPack).
It is possible to define a member template of IPack to define the Next IPack by adding an int to the arguments of the current IPack. So that one can progressively increase its number of arguments...
Here is maybe the missing piece. It is maybe something that most people don't realize.
(I mean, most of us uses it a lot with templates when, for example, the template accesses a member that one of its arguments must have, or when a trait tests for the existence of a specific overload, etc...)
But I think it might help in finding solutions sometimes to view it differently and say:
It is possible to declare an arbitrary type, built by assembling other types, for which the evaluation by the compiler can be delayed until it is effectively used.
Thus, it will be possible to inject the arguments of an IPack into another template...
Lastly, one should be able to detect if the operation succeeded with a testing trait making use of decltype and std::declval. (note: In the end, none of both have been used)
° Building Blocks
Step 1: IPack
template<typename...Ts>
struct IPack {
private:
template<typename U> struct Add1 {};
template<typename...Us> struct Add1<IPack<Us...>> { using Type = IPack<Us..., int>; };
public:
using Next = typename Add1<IPack<Ts...>>::Type;
static constexpr std::size_t Size = sizeof...(Ts);
};
using IPack0 = IPack<>;
using IPack1 = typename IPack0::Next;
using IPack2 = typename IPack1::Next;
using IPack3 = typename IPack2::Next;
constexpr std::size_t tp3Size = IPack3::Size; // 3
Now, one has a means to increase the number of arguments,
with a convenient way to retrieve the size of the IPack.
Next, one needs something to build an arbitrary type
by injecting the arguments of the IPack into another template.
Step 2: IPackInjector
An example on how the arguments of a template can be injected into another template.
It uses a template specialization to extract the arguments of an IPack,
and then, inject them into the Target.
template<typename P, template <typename...> class Target>
struct IPackInjector { using Type = void; };
template<typename...Ts, template <typename...> class Target>
struct IPackInjector<IPack<Ts...>, Target> { using Type = Target<Ts...>; };
template<typename T, typename U>
struct Victim;
template<typename P, template <typename...> class Target>
using IPInj = IPackInjector<P, Target>;
//using V1 = typename IPInj<IPack1, Victim>::Type; // error: "Too few arguments"
using V2 = typename IPInj<IPack2, Victim>::Type; // Victim<int, int>
//using V3 = typename IPInj<IPack3, Victim>::Type; // error: "Too many arguments"
Now, one has a means to inject the arguments of an IPack
into a Victim template, but, as one can see, evaluating Type
directly generates an error if the number of arguments does not
match the declaration of the Victim template...
Note
Have you noticed that the Victim template is not fully defined ?
It is not a complete type. It's only a forward declaration of a template.
The template to be tested will not need to be a complete type
for this solution to work as expected... ;)
If one wants to be able to pass this arbitrary built type to some detection trait one will have to find a way to delay its evaluation.
It turns out that the 'trick' (if one could say) is rather simple.
It is related to dependant names. You know this annoying rule
that enforces you to add ::template everytime you access a member template
of a template... In fact, this rule also enforces the compiler not to
evaluate an expression containing dependant names until it is
effectively used...
Oh I see ! ...
So, one only needs to prepare the IPackInjectors without
accessing its Type member, and then, pass it to our test trait, right ?
It could be done using something like that:
using TPI1 = IPackInjector<IPack1, Victim>; // No error
using TPI2 = IPackInjector<IPack2, Victim>; // No error
using TPI3 = IPackInjector<IPack3, Victim>; // No error
Indeed, the above example does not generate errors, and it confirms
that there is a means to prepare the types to be built and evaluate
them at later time.
Unfortunately, it won't be possible to pass these pre-configured
type builders to our test trait because one wants to use SFINAE
to detect if the arbitrary type can be instantiated or not.
And this is, once again, related to dependent name...
The SFINAE rule can be exploited to make the compiler silently
select another template (or overload) only if the substitution
of a parameter in a template is a dependant name.
In clear: Only for a parameter of the current template instantiation.
Hence, for the detection to work properly without generating
errors, the arbitrary type used for the test will have to be
built within the test trait with, at least, one of its parameters.
The result of the test will be assigned to the Success member...
Step 3: TypeTestor
template<typename T, template <typename...> class C>
struct TypeTestor {};
template<typename...Ts, template <typename...> class C>
struct TypeTestor<IPack<Ts...>, C>
{
private:
template<template <typename...> class D, typename V = D<Ts...>>
static constexpr bool Test(int) { return true; }
template<template <typename...> class D>
static constexpr bool Test(...) { return false; }
public:
static constexpr bool Success = Test<C>(42);
};
Now, and finally, one needs a machinery that will successively try
to instantiate our Victim template with an increasing number of arguments. There are a few things to pay attention to:
A template cannot be declared with no parameters, but it can:
Have only a parameter pack, or,
Have all its parameters defaulted.
If the test procedure begins by a failure, it means that the template must take more arguments. So, the testing must continue until a success, and then, continue until the first failure.
I first thought that it might make the iteration algorithm using template specializations a bit complicated... But after having thought a little about it, it turns out that the start conditions are not relevant.
One only needs to detect when the last test was true and next test will be false.
There must have a limit to the number of tests.
Indeed, a template can have a parameter pack, and such a template can receive an undetermined number of arguments...
Step 4: TemplateArity
template<template <typename...> class C, std::size_t Limit = 32>
struct TemplateArity
{
private:
template<typename P> using TST = TypeTestor<P, C>;
template<std::size_t I, typename P, bool Last, bool Next>
struct CheckNext {
using PN = typename P::Next;
static constexpr std::size_t Count = CheckNext<I - 1, PN, TST<P>::Success, TST<PN>::Success>::Count;
};
template<typename P, bool Last, bool Next>
struct CheckNext<0, P, Last, Next> { static constexpr std::size_t Count = Limit; };
template<std::size_t I, typename P>
struct CheckNext<I, P, true, false> { static constexpr std::size_t Count = (P::Size - 1); };
public:
static constexpr std::size_t Max = Limit;
static constexpr std::size_t Value = CheckNext<Max, IPack<>, false, false>::Count;
};
template<typename T = int, typename U = short, typename V = long>
struct Defaulted;
template<typename T, typename...Ts>
struct ParamPack;
constexpr std::size_t size1 = TemplateArity<Victim>::Value; // 2
constexpr std::size_t size2 = TemplateArity<Defaulted>::Value; // 3
constexpr std::size_t size3 = TemplateArity<ParamPack>::Value; // 32 -> TemplateArity<ParamPack>::Max;
° Conclusion
In the end, the algorithm to solve the problem is not that much complicated...
After having found the 'tools' with which it would be possible to do it, it only was a matter, as very often, of putting the right pieces at the right places... :P
Enjoy !
° Important Footnote
Here is the reason why the word intantiate(d) has been stricken at the places where it was used in relation to the Victim template.
The word instantiate(d) is simply not the right word...
It would have been better to use try to declare, or to alias the type of a future instantiation of the Victim template.
(which would have been extremely boring) :P
Indeed, none of the Victim templates gets ever instantiated within the code of this solution...
As a proof, it should be enough to see that all tests, made in the code above, are made only on forward declarations of templates.
And if you're still in doubt...
using A = Victim<int>; // error: "Too few arguments"
using B = Victim<int, int>; // No errors
template struct Victim<int, int>;
// ^^^^^^^^^^^^^^^^
// Warning: "Explicit instantiation has no definition"
In the end, there's a full sentence of the introduction which might be stricken, because this solution seems to demonstrate that:
It is possible to 'count' the number of parameters declared by a template...
Without instantiation of this template.
#include <utility>
#include <iostream>
template<template<class...>class>
struct ztag_t {};
template <template<class>class T>
constexpr std::size_t template_size_helper(ztag_t<T>) {
return 1;
}
template <template<class, class>class T>
constexpr std::size_t template_size_helper(ztag_t<T>) {
return 2;
}
template <typename T>
class MyTemplateClass { };
template <typename T, typename U>
class MyTemplateClass2 { };
template<template<class...>class T>
struct template_size:
std::integral_constant<
std::size_t,
template_size_helper(ztag_t<T>{})
>
{};
int main() {
std::cout << template_size<MyTemplateClass>::value << "\n";
std::cout << template_size<MyTemplateClass2>::value << "\n";
}
I know of no way without writing out the N overloads to support up to N arguments.
Live example.
Reflection will, of course, make this trivial.
° Before Reading This Post
This post does not answer to "How to get the number of parameters",
it answers to "how to get the number of arguments"...
It is let here for two reasons:
It might help someone who would have mixed up (like I did)
the meaning of parameters and arguments.
The techniques used in this post are closely related to the ones used
to produce the correct answer I've posted as a separate answer.
See my other answer for finding "the number of parameters" of a template.
The answer of Elliott looks more like what one usually does (though the primary template should be fully defined and "do something" IMHO). It uses a template specialization for when a template is passed as argument to the primary template.
Meanwhile, the answer of Elliott vanished...
So I've posted something similar to what he showed below.
(see "Generic Solution")
But, just to show you that you weren't that far from a working solution, and, because I noticed that you have used a function for your try, and, you declared this function constexpr, you could have written it like that:
Note
It is a 'fancy solution', but it works...
template <typename T> class MyTemplateClass {};
template <typename T, typename U> class MyTemplateClass2 {};
template <template <typename...> class T, typename...Ts>
constexpr const size_t template_size(T<Ts...> && v)
{
return sizeof...(Ts);
}
// If the target templates are complete and default constructible.
constexpr size_t size1 = template_size(MyTemplateClass<int>{});
constexpr size_t size2 = template_size(MyTemplateClass2<int, short>{});
// If the target templates are complete but NOT default constructible.
constexpr size_t size3 = template_size(decltype(std::declval<MyTemplateClass<int>>()){});
constexpr size_t size4 = template_size(decltype(std::declval<MyTemplateClass2<int, short>>()){});
Explanation
You said "because Ts can not be referenced", which is true and false, because of the way you made the declaration of template_size.
That is, a template template parameter cannot declare parameters itself (where you placed Ts in the declaration of the function template). It is allowed to do so to give a clue of what the template argument is expected to receive as argument, but it is not a declaration of a parameter name for the current template declaration...
(I hope it's clear enough) ;)
Obviously, it might be a little bit over complicated, but it worth knowing I think that such a construct is possible also... ;)
Generic Solution
template <typename T> class MyTemplateClass {};
template <typename T, typename U> class MyTemplateClass2 {};
template<typename T>
struct NbParams { static constexpr std::size_t Value = 0; };
template<template <typename...> class C, typename...Ts>
struct NbParams<C<Ts...>> { static constexpr std::size_t Value = sizeof...(Ts); };
constexpr size_t size1 = NbParams<MyTemplateClass<int>>::Value;
constexpr size_t size2 = NbParams<MyTemplateClass2<int, short>>::Value;
That is the regular way one does this kind of things... ;)

How to implement the generalized form of std::same_as (i.e. for more than two type parameters) that is agnostic to parameter order?

Background
We know that the concept std::same_as is agnostic to order (in other words, symmetric): std::same_as<T, U> is equivalent to std::same_as<U, T> (related question). In this question, I would like to implement something more general: template <typename ... Types> concept same_are = ... that checks whether types in the pack Types are equal to each other.
My attempt
#include <type_traits>
#include <iostream>
#include <concepts>
template <typename T, typename... Others>
concept same_with_others = (... && std::same_as<T, Others>);
template <typename... Types>
concept are_same = (... && same_with_others<Types, Types...>);
template< class T, class U> requires are_same<T, U>
void foo(T a, U b) {
std::cout << "Not integral" << std::endl;
}
// Note the order <U, T> is intentional
template< class T, class U> requires (are_same<U, T> && std::integral<T>)
void foo(T a, U b) {
std::cout << "Integral" << std::endl;
}
int main() {
foo(1, 2);
return 0;
}
(My intention here is to enumerate over every possible ordered pair of types in the pack)
Unfortunately, this code would not compile, with the compiler complaining that the call to foo(int, int) is ambiguous. I believe that it considers are_same<U, T> and are_same<T, U> as not equivalent. I would like to know why the code fails how I can fix it (so that the compiler treats them as equivalent)?
From cppreference.com Constraint_normalization
The normal form of any other expression E is the atomic constraint whose expression is E and whose parameter mapping is the identity mapping. This includes all fold expressions, even those folding over the && or || operators.
So
template <typename... Types>
concept are_same = (... && same_with_others<Types, Types...>);
is "atomic".
So indeed are_same<U, T> and are_same<T, U> are not equivalent.
I don't see how to implement it :-(
The problem is, with this concept:
template <typename T, typename... Others>
concept are_same = (... && std::same_as<T, Others>);
Is that the normalized form of this concept is... exactly that. We can't "unfold" this (there's nothing to do), and the current rules don't normalize through "parts" of a concept.
In other words, what you need for this to work is for your concept to normalize into:
... && (same-as-impl<T, U> && same-as-impl<U, T>)
into:
... && (is_same_v<T, U> && is_same_v<U, T>)
And consider one fold-expression && constraint to subsume another fold-expression constraint && if its underlying constraint subsumes the other's underlying constraint. If we had that rule, that would make your example work.
It may be possible to add this in the future - but the concern around the subsumption rules is that we do not want to require compilers to go all out and implement a full SAT solver to check constraint subsumption. This one doesn't seem like it makes it that much more complicated (we'd really just add the && and || rules through fold-expressions), but I really have no idea.
Note however that even if we had this kind of fold-expression subsumption, are_same<T, U> would still not subsume std::same_as<T, U>. It would only subsume are_same<U, T>. I am not sure if this would even be possible.
churill is right .Using std::conjunction_v might be helpful.
template <typename T,typename... Types>
concept are_same = std::conjunction_v<std::same_as<T,Types>...>;

Can I pattern-match a type without writing a custom trait class?

Since C++20 concepts aren't standardized yet, I'm using static_assert as a makeshift concept check, to provide helpful error messages if a type requirement isn't met. In this particular case, I have a function which requires that a type is callable before getting its result type:
template <typename F, typename... Args>
void example() {
static_assert(std::is_invocable_v<F, Args...>, "Function must be callable");
using R = std::invoke_result_t<F, Args...>;
// ...
}
In addition, I require that the callable's result must be some kind of std::optional, but I don't know what type the optional will hold, so I need to get that type from it:
using R = // ...
using T = typename R::value_type; // std::optional defines a value_type
However, this will fail if type R doesn't have a value_type, e.g. if it's not a std::optional as expected. I'd like to have a static_assert to check for that first, with another nice error message if the assertion fails.
I could check for an exact type with something like std::is_same_v, but in this case I don't know the exact type. I want to check that R is some instance of std::optional, without specifying which instance it must be.
One way to do that is with a helper trait:
template <typename T>
struct is_optional { static constexpr bool value = false; };
template <typename T>
struct is_optional<std::optional<T>> { static constexpr bool value = true; };
template <typename T>
constexpr bool is_optional_v = is_optional<T>::value;
…and then I can write:
static_assert(is_optional_v<R>, "Function's result must be an optional");
That works, but it seems a little awkward to pollute my namespace with a helper trait just for a one-off check like this. I don't expect to need is_optional anywhere else, though I can imagine possibly ending up with other one-off traits like is_variant or is_pair too.
So I'm wondering: is there a more concise way to do this? Can I do the pattern matching on instances of std::optional without having to define the is_optional trait and its partial specialization?
Following the suggestion by several respondents, I made a re-usable trait:
template <typename T, template <typename...> typename Tpl>
struct is_template_instance : std::false_type { };
template <template <typename...> typename Tpl, typename... Args>
struct is_template_instance<Tpl<Args...>, Tpl> : std::true_type { };
template <typename T, template <typename...> typename Tpl>
constexpr bool is_template_instance_v = is_template_instance<T, Tpl>::value;
…so that I can write:
static_assert(is_template_instance_v<R, std::optional>, "Function's result must be an optional");
This is just as many lines and declarations as the is_optional trait, but it's no longer a one-off; I can use the same trait for checking other kinds of templates (like variants and pairs). So now it feels like a useful addition to my project instead of a kluge.
Can I do the pattern matching on instances of std::optional without having to define the is_optional trait and its partial specialization?
Maybe using implicit deduction guides for std::optional?
I mean... something as
using S = decltype(std::optional{std::declval<R>()});
static_assert( std::is_same_v<R, S>, "R isn't a std::optional" );
Explanation.
When R is std::optional<T> for some T type, std::optional{r} (for an r value of type R) should call the copy constructor and the resulting value should be of the same type R.
Otherwise, the type should be different (std::optional<R>).
The following is a full compiling example.
#include <iostream>
#include <optional>
template <typename T>
bool isOptional ()
{
using U = decltype(std::optional{std::declval<T>()});
return std::is_same_v<T, U>;
}
int main ()
{
std::cout << isOptional<int>() << std::endl; // print 0
std::cout << isOptional<std::optional<int>>() << std::endl; // print 1
}
Anyway, I support the suggestion by super: create a more generic type-traits that receive std::option as template-template argument.

Can we use the detection idiom to check if a class has a member function with a specific signature?

Given a (reduced) implementation of the detection idiom
namespace type_traits
{
template<typename... Ts>
using void_t = void;
namespace detail
{
template<typename, template<typename...> class, typename...>
struct is_detected : std::false_type {};
template<template<class...> class Operation, typename... Arguments>
struct is_detected<void_t<Operation<Arguments...>>, Operation, Arguments...> : std::true_type {};
}
template<template<class...> class Operation, typename... Arguments>
using is_detected = detail::is_detected<void_t<>, Operation, Arguments...>;
template<template<class...> class Operation, typename... Arguments>
constexpr bool is_detected_v = detail::is_detected<void_t<>, Operation, Arguments...>::value;
}
we can easily check if a class foo contains a member function bar
struct foo {
int const& bar(int&&) { return 0; }
};
template<class T>
using bar_t = decltype(std::declval<T>().bar(0));
int main()
{
static_assert(type_traits::is_detected_v<bar_t, foo>, "not detected");
return 0;
}
However, as you can see, we cannot detect that foo::bar's argument type is int&&. The detection succeeds, cause 0 can be passed to foo::bar. I know that there are plenty of options to check for the exact signature of a (member) function. But I would like to know, if it's possible to modify this detection toolkit in order to detect that foo::bar's argument type is exactly int&&.
[I've created a live demo of this example.]
Without changing your type_traits, you may do
template<typename T, T> struct helper {};
template<class T>
using bar_t = decltype(helper<const int& (T::*)(int&&), &T::bar>{});
Demo
Adapting the ideas of dyp and Jarod42, I've came up with
template<class T, typename... Arguments>
using bar_t = std::conditional_t<
true,
decltype(std::declval<T>().bar(std::declval<Arguments>()...)),
std::integral_constant<
decltype(std::declval<T>().bar(std::declval<Arguments>()...)) (T::*)(Arguments...),
&T::bar
>
>;
Notice that bar_t will be the return type of a bar call. In this way, we stay consistent with the toolkit. We can detect the existence by
static_assert(type_traits::is_detected_v<bar_t, foo, int&&>, "not detected");
However, while this solution does exactly what I intended, I hate that I need to write "so much complicated code" for every method I want to detect. I've asked a new question targeting this issue.
I don't think this works for checking const qualifiers.
decltype(std::declval<T>().bar(std::declval<Arguments>()...)) (T::*)(Arguments...)
always produces a non-const function pointer type, whereas &T::bar will produce a const function pointer if bar is marked const.
This will then fail trying to convert the const pointer type to the non-const pointer type for storage in integral_constant.

Conditional compilation of templates

I am trying to get static_assert to help me avoid null pointers in C++11.
The problem seems to be that C++11 require the compiler to compile templates even if they are not instantiated.
I have the following code:
#include <type_traits>
template<typename T, typename... Us>
std::enable_if_t< std::is_constructible<T, Us...>::value == true, T * >
create_if_constructible(Us... args) { return new T(args...); }
template<typename T, typename... Us>
std::enable_if_t< std::is_constructible<T, Us...>::value == false, T * >
create_if_constructible(Us... args) {
static_assert( false, "Class T constructor does not match argument list.");
return nullptr;
}
struct ClassA {
ClassA(int a, string b) {}
};
void foo() {
ClassA *a = create_if_constructible<ClassA>(1, "Hello");
// ClassA *b = create_if_constructible<ClassA>(1, "Hello", "world"); // I want compile time error here.
}
I would like this to compile without error. But the static_assert is compiled and gives me a compile time error.
Only if the the second instantiation of the ClassA is in the code should it give me a compile time error.
The standard permits, but does not require, compilers to diagnose templates for which no valid instantiation can be generated. This can range from simple syntax errors to your example of a constant false expression in a static_assert. §14.6 [temp.res]/p8:
If no valid specialization can be generated for a template, and that
template is not instantiated, the template is ill-formed, no
diagnostic required.
I'm rather baffled by all this SFINAE machinery, though. A simple
template<typename T, typename... Us>
T* create_if_constructible(Us... args) { return new T(args...); }
already refuses to compile if T is not constructible from the parameter given, so I'm not sure how this complex circumlocution will help you "avoid null pointers".
Regardless, a simple way to make choosing the second function template a compile-time error is to explicitly delete it.
template<typename T, typename... Us>
std::enable_if_t< std::is_constructible<T, Us...>::value == false, T * >
create_if_constructible(Us... args) = delete;
Alternatively, if you are partial to static_asserts, perhaps because of the custom error message, you must ensure that there is theoretically a way to generate a valid instantiation of your template. That means that 1) what you are static_asserting on must depend on a template argument, and 2) there must be theoretically a way for the condition to be true. A simple way is to use an auxiliary template:
template<class> class always_false : std::false_type {};
template<typename T, typename... Us>
std::enable_if_t< std::is_constructible<T, Us...>::value == false, T * >
create_if_constructible(Us... args) {
static_assert( always_false<T>::value, "Class T constructor does not match argument list.");
return nullptr;
}
The key point here is that the compiler cannot assume that always_false<T>::value is always false because it is always possible that there's a specialization later that sets it to true, and so it is not allowed to reject this at template definition time.
In all C++ standards ever, templates are compiled in two phases. The second phase is instantiation, but compilation can also fail in phase 1. In particular, syntax errors are detected in phase 1.
In your case, the simpler solution is to leave out the body of the second instantiation.
Another solution is to use T in the static_assert, so the compiler must delay evaluation to phase 2. Trivially: static_assert(sizeof(T)==0,