std::optional specialization for reference types
When n3406 (revision #2 of the proposal) was discussed, some committee members were uncomfortable with optional references. In n3527 (revision #3), the authors decided to make optional references an auxiliary proposal, to increase the chances of getting optional values approved and put into what became C++14. While optional didn't quite make it into C++14 for various other reasons, the committee did not reject optional references and is free to add optional references in the future should someone propose it.
Why GCC rejects std::optional for references?
Because optional
, as standardized in C++17, does not permit reference types. This was excluded by design.
There are two reasons for this. The first is that, structurally speaking, an optional<T&>
is equivalent to a T*
. They may have different interfaces, but they do the same thing.
The second thing is that there was effectively no consensus by the standards committee on questions of exactly how optional<T&>
should behave.
Consider the following:
optional<T&> ot = ...;
T t = ...;
ot = t;
What should that last line do? Is it taking the object being referenced by ot
and copy-assign to it, such that *ot == t
? Or should it rebind the stored reference itself, such that ot.get() == &t
? Worse, will it do different things based on whether ot
was engaged or not before the assignment?
Some people will expect it to do one thing, and some people will expect it to do the other. So no matter which side you pick, somebody is going to be confused.
If you had used a T*
instead, it would be quite clear which happens:
T* pt = ...;
T t = ...;
pt = t; //Compile error. Be more specific.
*pt = t; //Assign to pointed-to object.
pt = &t; //Change pointer.
Specializing std::optional
The general rule in 17.6.4.2.1 [namespace.std]/1 applies:
A program may add a template specialization for any standard library template to namespace
std
only if the declaration depends on a user-defined type and the specialization meets the standard library requirements for the original template and is not explicitly
prohibited.
So I would say it's allowed.
N.B. optional
will not be part of the C++14 standard, it will be included in a separate Technical Specification on library fundamentals, so there is time to change the rule if my interpretation is wrong.
How to make a class template specialization for reference and non-reference types?
So I found a way that suits my needs and that is basically the combination of @Kevin's answer and @Sam Varshavchik's comment on the question. So I'm using std::shared_ptr
with Class template argument deduction guides. Here's my solution:
template<typename> // Base Condition
class Drawable {};
/* Lvalue */
template<>
class Drawable<Pen &> {
Pen &m_pen;
public:
Drawable(Pen &a)
:m_pen(a) {}
};
/* Rvalue */
template<>
class Drawable<Pen &&> {
Pen m_pen;
public:
Drawable(Pen &&a)
:m_pen(std::move(a)) {}
};
/* Shared */
template<>
class Drawable<std::shared_ptr<Pen>> {
std::shared_ptr<Pen> m_pen;
public:
Drawable(std::shared_ptr<Pen> pen)
:m_pen(pen) {}
};
/* These guides help out the compiler determine the right specialization */
Drawable(Pen &) -> Drawable<Pen &>;
Drawable(Pen &&) -> Drawable<Pen &&>;
Drawable(std::shared_ptr<Pen>) -> Drawable<std::shared_ptr<Pen>>;
All this is, is 3 specializations for the 3 cases I want to support and guides to help the compiler decide which 1 the compiler should pick. Now the user can pass in a existing or temporary Pen or a existing or temporary std::shared_ptr
to a Pen and everything just works without the user having to specify any template arguments.
To remove repeating code just move it into a base class and then inherit from it in all 3 cases.
By the way that template argument doesn't have to exactly match what you're passing in. I just made it that way to make the code more readable (ig). You can also get away by doing something like this.
template<int>
class Drawable {};
/* Lvalue */
template<>
class Drawable<1> {
Pen &m_pen;
public:
Drawable(Pen &a)
:m_pen(a) {}
Pen &get() { return m_pen; }
};
/* Rvalue */
template<>
class Drawable<2> {
Pen m_pen;
public:
Drawable(Pen &&a)
:m_pen(std::move(a)) {}
Pen &get() { return m_pen; }
};
/* Shared */
template<>
class Drawable<3> {
std::shared_ptr<Pen> m_pen;
public:
Drawable(std::shared_ptr<Pen> pen)
:m_pen(pen) {}
std::shared_ptr<Pen> &get() { return m_pen; }
};
Drawable(Pen &) -> Drawable<1>;
Drawable(Pen &&) -> Drawable<2>;
Drawable(std::shared_ptr<Pen>) -> Drawable<3>;
Here instead of using types are template parameters I'm using numbers also don't use this method as you don't understand whats going on. This was just an example to demonstrate a point.
So now you main()
can look something like this:
int main() {
Pen ref(69);
Drawable a(Pen(69));
Drawable b(ref);
Drawable c(std::make_shared<Pen>(33));
ref = 420; // Now the Pen inside Drawable is also 420
}
Thanks for the help @Kevin, @Sam Varshavchik and @Aconcagua
Use of std::optional to pass a std::vector int to a functional by reference
From what I'm aware, this isn't possible in the standard as one hasn't agreed upon the effects of an assignment.
What you want to achieve is possible with a library feature:
void test_func(std::optional<std::reference_wrapper<std::vector<int>>> vec)
How to specialize classes for all reference types C++03
As seen (correctly) in this thread Specializing function template for reference types, the remove_reference suggested here won't work. It just won't go into the second implementation EVER, because the compiler sees T& and T just the same.
Instead you could MANUALLY tell the compiler that it is now dealing with a reference type, using the same specialization trick
template<typename T, bool isReference>
class A {
};
template<typename T>
class A<T,false>{
private:
T m_member;
public:
A(T _member);
//... MORE STUFF
void foo(T param);
}
/////////////////////////
template<typename T>
class A<T,true>{
private:
T& m_member;
public:
A(T& _member);
//... MORE STUFF
void foo(T param);
}
If you want to extract some similar behavior and avoid the code dupelication this solution causes, you could easily extract that behavior to a Base Class<T>
, and do
template<typename T,bool isReference>
class A : public BaseClass<T>{
}
and so on.
Usage would be
main.cpp
A<int,false> a1;//1st version
A<int&,true> a2;//2nd version
A<B,false> a3;//1st version
A<B&,true> a4;//2nd version
A<C*,false> a5;//1st version, as pointers are value types
Template specialization and references
only the general (or primary? What is the correcto word here?) template
The technical term used by the C++ Standard is "primary class template". It will also be the most general class template, compared to its partial specializations and explicit specializations. So that could also be a reasonable thing to call it, given enough context.
The "reference collapsing rule" is found in [dcl.ref]/6 and applies mainly when determining the meaning of combining a specific type name which aliases a reference type with a &
or &&
token which would normally form a reference to the type name's type. Deducing template arguments for a template parameter of the form T&
or T&&
is sort of the reverse of that. Although it's helpful to think of template argument deduction as "find the template arguments so that the resulting types match up", the technical details of template argument deduction are much more specific; [temp.deduct] is several pages of rules for exactly how this deduction proceeds, and there are additional relevant rules in other sections. The detail is needed so compilers agree on cases when there could otherwise be more than one "correct" answer, and so that compilers aren't required to deal with some of the more difficult cases.
In particular, when matching a dependent type P
with a known type A
, by the list of deducible types in [temp.deduct.type]/8, deduction can occur if both P
and A
have the form T&
or if both have the form T&&
. When attempting argument deduction for the partial specialization remove_reference<T&&>
to determine the definition of remove_reference<int&>
, P
is T&&
and A
is int&
, so they do not share one of these forms.
The template argument deduction rules do not have a general allowance for deducing arguments from a reverse of the reference collapsing rule. But they do have a limited allowance which is related for certain cases: Per [temp.deduct.call]/3, if T
is a template type parameter, but not a parameter for a class template, then the type T&&
is a forwarding reference. When comparing types for argument deduction, if P=T&&
is a forwarding reference type and A
is an lvalue reference type, then the template type parameter T
can be deduced as the lvalue reference type A
, only if A
is the type of an lvalue function argument expression ([temp.deduct.call]/3 again) or sometimes if P
and A
are being compared because they represent function parameter types within two compared function types ([temp.deduct.type]/10).
Similarly, when ["]calling["]
remove_reference_t<int&&>
, can't the first specialization'sT&
matchint&&
ifT
is substituted forT&
?
In this case, there's no possible way that the partial specialization remove_reference<T&>
can match remove_reference<int&&>
. Even if the process of template argument deduction allowed finding a potential answer for this case, there is no possible type T
such that T&
is the same as int&&
.
Allow Only Explicit Specialization of Template Class
There are a few ways how you could accomplish this.
1. Explicitly list the acceptable enums
One way would be to explicitly list the acceptable enums in your static_assert
:
godbolt
#include <type_traits>
template<class T, class... Other>
constexpr bool is_same_one_of = (std::is_same_v<T, Other> || ...);
enum Enum1 {};
enum Enum2 {};
enum Enum3 {};
template<class T>
class kvEnumHelper {
static_assert(
is_same_one_of<T, Enum1, Enum2 /* , ... more enum types ... */>,
"T must be either Enum1 or Enum2"
);
/* ... actual implementation ... */
};
kvEnumHelper<Enum1> foo1; // ok
kvEnumHelper<Enum2> foo2; // ok
kvEnumHelper<Enum3> foo3; // compile error
2. Inherit implementation
Another option would be to move the actual implementation into a separate class and only make the specializations inherit from the implementation class.
godbolt
#include <type_traits>
enum Enum1 {};
enum Enum2 {};
enum Enum3 {};
template<class T>
class kvEnumHelper {
static_assert(
!std::is_same_v<T, T>, // always false, but only when actually instanciated
"Enum Class is not supported"
);
};
template<class TEnum>
class kvEnumHelperImpl {
/* ... actual implementation ... */
};
template<> class kvEnumHelper<Enum1> : public kvEnumHelperImpl<Enum1> {};
template<> class kvEnumHelper<Enum2> : public kvEnumHelperImpl<Enum2> {};
kvEnumHelper<Enum1> foo1; // ok
kvEnumHelper<Enum2> foo2; // ok
kvEnumHelper<Enum3> foo3; // compile error
3. Using an additional trait
Yet another alternative would be to use a trait that can specialized for the enum types you would want to be usable with kvEnumHelper
.
godbolt
template <class T>
constexpr bool allow_enum_helper = false;
enum Enum1 {};
enum Enum2 {};
enum Enum3 {};
template<class T>
class kvEnumHelper {
static_assert(
allow_enum_helper<T>,
"Enum Class is not supported"
);
/* ... actual implementation ... */
};
template<>
constexpr bool allow_enum_helper<Enum1> = true;
template<>
constexpr bool allow_enum_helper<Enum2> = true;
kvEnumHelper<Enum1> foo1; // ok
kvEnumHelper<Enum2> foo2; // ok
kvEnumHelper<Enum3> foo3; // compile error
If you already have a function like getEnumFromString
that is deleted and has specializations for the allowable enum types you could use that to detect if kvEnumHelper<T>
should be allowed by detecting if the function is deleted or not.
godbolt
#include <string>
enum Enum1 {};
enum Enum2 {};
enum Enum3 {};
template<typename T>
T getEnumFromString(const std::string& in_string) = delete; // only allow templates we define (catches them at compile time)
template<> Enum1 getEnumFromString(const std::string& in_string);
template<> Enum2 getEnumFromString(const std::string& in_string);
template<class T>
constexpr bool allow_enum_helper = requires { getEnumFromString<T>(std::string{}); };
template<class T>
class kvEnumHelper {
static_assert(
allow_enum_helper<T>,
"Enum Class is not supported"
);
/* ... actual implementation ... */
};
kvEnumHelper<Enum1> foo1; // ok
kvEnumHelper<Enum2> foo2; // ok
kvEnumHelper<Enum3> foo3; // compile error
Function with single optional parameter and default value in template function
Mmm the other answer is close. But not quite there. f(12)
doesn't "try to instantiate f<int&&>
". In fact, it fails to deduce T because T is in non-deduced context.
Also, your question was beside the point: even without a default value you have the same problem: Compiler Explorer
template <typename T> void f(boost::optional<T> v) {
if (v) {
std::cout<<"value: " << v.get() << "\n";
} else {
std::cout<<"no value\n";
}
}
int main()
{
f(12);
f("string");
}
Now, before I blindly show you how you can fix all that, ask yourself the question: What are we doing here.
If you want default arguments, doesn't that by definition mean that they aren't optional values? Maybe you simply need: Compiler Explorer
template <typename T> void f(T const& v) {
std::cout << "value: " << v << "\n";
}
void f() {
std::cout << "no value\n";
}
int main()
{
f(12);
f("string");
f();
}
Printing
value: 12
value: string
no value
With some hackery you can combine the overloads by defaulting the template type argument:
template <typename T = struct not_given*> void f(T const& v = {}) {
if constexpr(std::is_same_v<T, not_given*>) {
std::cout << "no argument\n";
} else {
std::cout << "value: " << v << "\n";
}
}
Prints Compiler Explorer
value: 12
value: string
no argument
What If You Require optional<>
In that case, in you specific example you would probably want optional<T const&>
to avoid needlessly copying all the arguments; but see std::optional specialization for reference types.
If You Really Really Want¹
Say, you MUST have the semantics you were looking for. You do not care that you won't be able to know the difference between calling with no argument vs. calling with an uninitialized optional (none
). This is kinda like many scripting languages, right?
Now you have to make the template argument become deduced context, and then want to ensure that... it is an optional<T>
:
template <typename T, typename = void> struct is_optional : std::false_type { };
template <typename T> struct is_optional<boost::optional<T>> : std::true_type { };
template <typename T = boost::optional<void*> >
std::enable_if_t<is_optional<T>::value> f(T const& v = {}) {
if (v) {
std::cout << "value: " << *v << "\n";
} else {
std::cout << "no value\n";
}
}
template <typename T>
std::enable_if_t<not is_optional<T>::value> f(T const& v) {
return f(boost::make_optional(v));
}
int main()
{
f(12);
f("string");
f();
}
One "advantage" is that that now you clearly see the copying being done.
Another "advantage" is that now you can support std::optional
the same way: https://godbolt.org/z/1Mhja83Wo
template <typename T> struct is_optional<std::optional<T>> : std::true_type { };
Summary
I hope this answer gets the point across that C++ is not a dynamically typed language. This implies that the idea of optional arguments of "unknown" type is really not idiomatic. (It might be a bit unfortunate that Boost called it boost::none
instead of e.g. std::nullopt
, perhaps giving people associations with Python's None
.)
Instead, you can use static polymorphism. The simplest version of that was the first I showed, using function overloading.
If you were to mimic a dynamic type interface in C++, you would probably use std::variant
or std::any
instead. To restrict the bound types you would use concepts (this is getting a bit deep, but see e.g. Boost Type Erasure).
¹ i really really really wanna zig a zig ah
Related Topics
Detect If Program Is Running with Full Administrator Rights
How to Delete a Non-New Object
How to Add 2 Arbitrarily Sized Integers in C++
Forward Declare a Standard Container
Why Doesn't My Template Accept an Initializer List
Converting String of 1S and 0S into Binary Value
Construct Path for #Include Directive with MACro
How to Cout a Float Number with N Decimal Places
False Positive with Is_Copy_Constructible on Vector<Unique_Ptr>
Std::Strings's Capacity(), Reserve() & Resize() Functions
C++ Visual Studio Character Encoding Issues
How to Declare Constexpr Extern
Why Cannot a Non-Member Function Be Used for Overloading the Assignment Operator
Why Static Variable Needs to Be Explicitly Defined