Partial specialization or instantiation of template class method - c++

I have template struct with several template parameters
template<class Result, class T, class K>
struct MyClass
{
public:
Result foo()
{
return Result{};
}
};
This struct works fine for all templates except the case when Result is void.
I understand, that Result{} cannot be implemented to void type, so my current solution is to use partial specialization like this:
template<class T, class K>
struct MyClass<void, T, K>
{
public:
void foo()
{
return;
}
};
This allows to do following:
int main()
{
MyClass<void, double, char> mycl1;
MyClass<int, double, char> mycl2;
mycl1.foo();
mycl2.foo();
}
Is there a way to make mycl1.foo() compile without partial class specialization in C++ 14 standart? I could use if constexr and type trait is_void_v combination, but I want to find if there is a way to:
specialization of template class method partial explicit
instantiation of template class method

While you cannot do
Result foo()
{
return Result{};
}
If Result is void, you can use
Result foo()
{
return Result();
}
The behavior in this case is the same and you will get a value initialized object returned. This syntax is allowed when Result is void by [expr.type.conv]\2
If the initializer is a parenthesized single expression, the type conversion expression is equivalent (in definedness, and if defined in meaning) to the corresponding cast expression. If the type is cv void and the initializer is (), the expression is a prvalue of the specified type that performs no initialization. Otherwise, the expression is a prvalue of the specified type whose result object is direct-initialized with the initializer. For an expression of the form T(), T shall not be an array type.
Coming soon though you will be able to use
return Result{};
even if Result is void as C++20 added to that section that {} will work as well for void. [expr.type.conv]\2 now states
If the initializer is a parenthesized single expression, the type conversion expression is equivalent to the corresponding cast expression. Otherwise, if the type is cv void and the initializer is () or {} (after pack expansion, if any), the expression is a prvalue of the specified type that performs no initialization. Otherwise, the expression is a prvalue of the specified type whose result object is direct-initialized with the initializer. If the initializer is a parenthesized optional expression-list, the specified type shall not be an array type.

Related

How to define equivalent rule for non type template arg

After some time of figuring out my question I have found it's really fascinating how compiler can deduce template arguments from set of "tags" (non-type template args). But it looks like the compiler only understands "byte by byte" equality rule.
What I mean is this code:
struct S1 {
constexpr S1(int v) : _v(v)
{};
constexpr bool operator == (const S1& other) { return true;}
int _v;
};
template <S1 Tag, typename ValueType>
struct TagedValue { ValueType value; };
struct Test : TagedValue<S1{0}, int>, TagedValue<S1{1}, double> {};
template <S1 Idx, typename T>
auto& get(TagedValue<Idx, T>& test) {
return test.value;
}
int main()
{
Test test;
get<S1{1}>(test); // Still compiles, even though S1{0} == S1{1}
static_assert(S1{0} == S1{1});
}
As you can see I have defined my own operator == which basically says: "any two instances are equal". But it looks like in order to deduce T in get function the compiler still checks whether or not the actual content of the struct is the same. That is, for the purpose of type deduction S1{0} != S2{1}, even though S1{0} == S2{1} from "C++ point of view".
My question is: Is there any way I can redefine that "type deduction equality rule"?
UPD: To make it probably more clear, if I replace this line
struct Test : TagedValue<S1{0}, int>, TagedValue<S1{1}, double> {};
with
struct Test : TagedValue<S1{1}, int>, TagedValue<S1{1}, double> {};
the compiler gives an error, complaining about ambiguty.
Template parameter deduction, and other template-related things, use the concept of type equivalence. The parts relevant to your question are these:
temp.type/1 Two template-ids are the same if
...
(1.3) - their corresponding non-type template-arguments are template-argument-equivalent (see below) after conversion to the type of the template-parameter...
temp.type/2 Two values are template-argument-equivalent if they are of the same type and
...
(2.10) - they are of class type and their corresponding direct subobjects and reference members are template-argument-equivalent.
Roughly, it's member-wise equality. No, you cannot override it.
You can not override the equality check for template parameter objects:
An identifier that names a non-type template parameter of class type T denotes a static storage duration object of type const T, called a template parameter object, whose value is that of the corresponding template argument after it has been converted to the type of the template parameter. All such template parameters in the program of the same type with the same value denote the same template parameter object. A template parameter object shall have constant destruction.
https://en.cppreference.com/w/cpp/language/template_parameters

Using std::is_same with structural (non-type) template parameters

Consider a templated type containing a structural template parameter of any type. For the purpose of the example value_type<auto V> is defined.
We also declare a constexpr structure containing some member integral types with custom constructors that set the member values using a non-trivial expression (requiring more than memory copy).
Try feeding the structure into value_type and comparing it against another instance using std::is_same.
Example code to illustrate the case:
#include <iostream>
#include <type_traits>
template <auto V>
struct value_type
{
using type = decltype(V);
static constexpr type value = V;
};
struct hmm
{
//Both constructors set b=4 by default
constexpr hmm(int x, int y = 8) : a(x), b(y / 2) { }
constexpr hmm(float c, int z = 2) : a((int)c), b(z * 2) { }
const int a;
const int b;
friend constexpr bool operator==(const hmm& a, const hmm& b) { return false; }
friend constexpr bool operator!=(const hmm& a, const hmm& b) { return true; }
};
int main()
{
std::cout << (std::is_same_v<value_type<hmm(2)>, value_type<hmm(3.5f)>>) << ", ";
std::cout << (std::is_same_v<value_type<hmm(5)>, value_type<hmm(5.11112f)>>) << ", ";
std::cout << (std::is_same_v<value_type<hmm(5, 7)>, value_type<hmm(5.11112f)>>) << ", ";
std::cout << (std::is_same_v<value_type<hmm(5, 12)>, value_type<hmm(5.11112f, 3)>>) << std::endl;
return 0;
}
This code prints 0, 1, 0, 1 on gcc, msvc, and clang. It makes perfect sense, however, it got me wondering what are the limits of this mechanism.
How exactly is the comparison of those types performed?
Is this behavior standardized across compilers or is it just pure luck that they all seem to follow the same pattern here?
From what it looks like, their members are checked after construction, however apparently without using the comparison operators.
Is there a standard compliant way to override this comparison?
And, more generally, according to a potential is_same implementation using <T,T> specialization (source):
template<class T, class U>
struct is_same : std::false_type {};
template<class T>
struct is_same<T, T> : std::true_type {};
What are the rules for matching types T, U to be considered the same entity in the context of <T,T> specialization?
How exactly is the comparison of those types performed?
Is this behavior standardized across compilers or is it just pure luck that they all seem to follow the same pattern here?
As per NTTP on cppref, emphasis mine:
An identifier that names a non-type template parameter of class type T denotes a static storage duration object of type const T, called a template parameter object, whose value is that of the corresponding template argument after it has been converted to the type of the template parameter. All such template parameters in the program of the same type with the same value denote the same template parameter object. A template parameter object shall have constant destruction.
And type equivalence:
Template argument equivalence is used to determine whether two template-ids are same.
Two values are template-argument-equivalent if they are of the same type and
they are of integral or enumeration type and their values are the same
or they are of pointer type and they have the same pointer value
or they are of pointer-to-member type and they refer to the same class member or are both the null member pointer value
or they are of lvalue reference type and they refer to the same object -> - or function
or they are of type std::nullptr_t
or they are of floating-point type and their values are identical
or they are of array type (in which case the arrays must be member objects of some class/union) and their corresponding elements are template-argument-equivalent
or they are of union type and either they both have no active member or they have the same active member and their active members are template-argument-equivalent
or they are of floating-point type and their values are identical
or they are of non-union class type and their corresponding direct subobjects and reference members are template-argument-equivalent
There's no comparison but only type equivalence involved. If they have the same value, they all refer to the same object. That's it.
In your case, hmm(5) and hmm(5.11112f) refers to the same object, but not between hmm(2) and hmm(3).
Is there a standard compliant way to override this comparison?
I believe it's not allowed now.
What are the rules for matching types T, U to be considered the same entity in the context of <T,T> specialization?
They have the exact same type with cv-quailifiers.

Return a Type Even if Type is void [duplicate]

I have template struct with several template parameters
template<class Result, class T, class K>
struct MyClass
{
public:
Result foo()
{
return Result{};
}
};
This struct works fine for all templates except the case when Result is void.
I understand, that Result{} cannot be implemented to void type, so my current solution is to use partial specialization like this:
template<class T, class K>
struct MyClass<void, T, K>
{
public:
void foo()
{
return;
}
};
This allows to do following:
int main()
{
MyClass<void, double, char> mycl1;
MyClass<int, double, char> mycl2;
mycl1.foo();
mycl2.foo();
}
Is there a way to make mycl1.foo() compile without partial class specialization in C++ 14 standart? I could use if constexr and type trait is_void_v combination, but I want to find if there is a way to:
specialization of template class method partial explicit
instantiation of template class method
While you cannot do
Result foo()
{
return Result{};
}
If Result is void, you can use
Result foo()
{
return Result();
}
The behavior in this case is the same and you will get a value initialized object returned. This syntax is allowed when Result is void by [expr.type.conv]\2
If the initializer is a parenthesized single expression, the type conversion expression is equivalent (in definedness, and if defined in meaning) to the corresponding cast expression. If the type is cv void and the initializer is (), the expression is a prvalue of the specified type that performs no initialization. Otherwise, the expression is a prvalue of the specified type whose result object is direct-initialized with the initializer. For an expression of the form T(), T shall not be an array type.
Coming soon though you will be able to use
return Result{};
even if Result is void as C++20 added to that section that {} will work as well for void. [expr.type.conv]\2 now states
If the initializer is a parenthesized single expression, the type conversion expression is equivalent to the corresponding cast expression. Otherwise, if the type is cv void and the initializer is () or {} (after pack expansion, if any), the expression is a prvalue of the specified type that performs no initialization. Otherwise, the expression is a prvalue of the specified type whose result object is direct-initialized with the initializer. If the initializer is a parenthesized optional expression-list, the specified type shall not be an array type.

Pass a reference to a base class as template parameter

I have the following code where I try to specialize a function template for each instance of a class (that may have been derived):
class Base {
};
class Derived:public Base {
};
template<Base& b>
void myfunction() {
//use b somehow
}
Derived myobject;
int main() {
myfunction<myobject>(); //this does not work
}
The code results in error message:
candidate template ignored: invalid explicitly-specified argument for template parameter 'b'
[live demo]
How do I pass a reference to a static instance of type Base, given the static Derived object myobject?
While it is fine to declare a template non-type parameter as a reference according to [temp.param]/4:
A non-type template-parameter shall have one of the following
(optionally cv-qualified) types:
...
lvalue reference to object or lvalue reference to function,
...
The argument must follow the restrictions in [temp.arg.nontype]/2:
A template-argument for a non-type template-parameter shall be a
converted constant expression of the type of the template-parameter.
For a non-type template-parameter of reference or pointer type, the
value of the constant expression shall not refer to (or for a pointer
type, shall not be the address of):
a subobject,
...
Which explicitly forbids what you are trying to do. Since b is going to end up referring to a sub-object.
The only solution which will make this compile, is adding another overload:
template<Derived & d>
void myfunction()
{
//use d somehow
}
So you'll need to extract the common code out somehow.
Or, if you have C++17 available:
template<auto& b, std::enable_if_t<
std::is_base_of_v<Base, std::decay_t<decltype(b)>>
, void*> = nullptr>
void myfunction()
{
//use b somehow
}
I suggest you re-think your general approach, however.

C++ constructor template specialization

I'm trying to create a specialized constructor for std::string arguments, but the other one is always used when I call it with a string argument.
struct Literal : Expression
{
template <typename V>
Literal(V val)
{
value = val;
}
};
template <>
Literal::Literal(std::string const& val)
{
value = val.c_str();
}
It doesn't matter if both are defined inside the class, both outside the class, or like in the posted example only the specialization is defined outside the class: When called with std::string, the assignment value = val gives a compiler error.
How do I correctly specialize this constructor template for std::string?
You don't.
You should overload the constructor: Literal(const std::string&), which you can do in the struct declaration.
The compiler always tries to match non-template overloads before template ones.
According to the standard, 14.8.2.1 Deducing template arguments from a function call [temp.deduct.call] where P is the template parameter and A is the function-call argument in that position:
2 If P is not a reference type:
If A is an array type, the pointer type produced by the array-to-pointer = standard conversion ([conv.array]) is used in place of A for type deduction; otherwise,
If A is a function type, the pointer type produced by the function-to-pointer standard conversion ([conv.func]) is used in place of A for type deduction; otherwise,
If A is a cv-qualified type, the top-level cv-qualifiers of A's type are ignored for type deduction.
If P is a cv-qualified type, the top-level cv-qualifiers of P's type are ignored for type deduction. If P is a reference type, the type referred to by P is used for type deduction. [...]
So given
std::string s{"hello"};
const std::string& sr{s};
Literal l(sr);
A (sr) is const std::string& but the constness is not considered, so the compiler considered std::string. This matches your
template <typename V>
Literal(V val)
{
value = val;
}
and so it uses this specialization. If you had specialized
template<>
Literal(std::string val)
the compiler would find this specialization, and this is probably what you will have to do and use move semantics.
#include <iostream>
#include <string>
struct S {
template<typename T>
S(T t) { std::cout << "T t\n"; }
std::string value_;
};
template<>
S::S(std::string value) {
std::cout << "string\n";
value_ = std::move(value);
}
template<>
S::S(const std::string&) {
std::cout << "const string&\n";
}
int main() {
S s1(42);
std::string foo{"bar"};
const std::string& foor = foo;
S s2(foo);
S s3(foor);
}
http://ideone.com/eJJ5Ch
Many times, when overload is a solution, people try to define full specialization. But overload could be a much better solution. In your case, I would create a new constructor with the string parameter.
Remember that only base template is considered in overload resolution. The following article is a good reference to understand this idea:
http://www.gotw.ca/publications/mill17.htm
UPDATE:
Anyway, to improve my answer, you could try the following base template constructor:
template <typename V>
Literal(V const& val)
{
value = val;
}