I'm having a problem with an enable_if construct which I've managed to reduce to a very simple piece of failing code:
template <typename Enable, typename...Args>
struct Get;
template <typename FirstArg, typename... OtherArgs>
struct Get<typename std::enable_if<true>::type, FirstArg, OtherArgs...>
{
using type = FirstArg;
};
(Live example)
The above code is a gutted version of some code that did something useful, but for the sake of this question I'm more interested in why this doesn't work, not whether or not it's ideal or does anything useful. The meta function Get should take the first type passed to it and, based on some condition (in this case always true as I didn't construct a case for when it is not) return it.
I don't want the first argument to resolve to the enable_if condition, that should be completely abstracted.
When I try to run this (see live example) it produces
error: ‘type’ in ‘struct Get’ does not name a type using T =
typename Get::type
Why?
The specialization you wrote only kicks in if someone passed void as the first argument to Get.
In your example code, you passed int. So the specialization does not apply.
The names of the types in the template definition and specialization have no connection, just their position in the Get<blah, blah, blah>. And it is pattern matching off the primary definition.
int main() {
using T = Get<void, int>::type;
}
Related
I am very new to c++ and am trying to understand how "generic" types are "enforced" in templates, specifically with something like charT.
After reading this question I understand that charT can be any char-like object, but I am wondering what the appropriate way is to check that the user actually supplied a valid charT.
In other words, what happens if you provide a double instead? Do you just rely on the complier throwing an error when the function inevitably tries to do something invalid with the double? Or is there a formal way to check that the supplied type is valid?
Generally, this is can be done in C++ using type traits, std::enable_if and SFINAE (Substitution Failure Is Not An Error), assuming that you're using pre-C++20 code. The basic principle is to check if a type has a certain property and if it doesn't, to disable a function overload or class specialization.
For example, if you want to write a function template that only makes sense for classes derived from a certain base class, you can write:
#include <type_traits>
struct Base {};
struct Derived : public Base {};
template<typename T, typename = std::enable_if_t<std::is_base_of_v<Base, T>>>
void foo(const T&) {
}
int main() {
Derived d{};
foo(d);
//foo(1); <-- ERROR: requirement not fulfilled
return 0;
}
Here, a second, unnamed template parameter is used and assigned to the type "returned" by std::enable_if_t (this is shorthand for typename std::enable_if<...>::type to save one from typing out all that and is available as of C++14). std::enable_if will only have the type definition called type if the first parameter evaluates to true. In this example, we check if std::is_base_of_v<Base, T> is true. If that is the case, then std::enable_if will be instantiated to have a using type = void; declaration (if one desires different type than void, this can be passed to std::enable_if as a second parameter right after the bool parameter). Therefore, the unnamed parameter will be set to void and the program compiles. If, however, the first parameter evaluates to false, there will be no type definition in std::enable_if, and the function template will not be instantiated, because the type for the second parameter in our template cannot be substituted, which will merely remove the function from the overload set and not cause a compiler error. This is what SFINAE means. Of course if no other viable overload can be found, you will still get an error, but not due to the fact that the template instantiation failed.
If you uncomment the call to foo(1), clang, for instance, will say:
<source>:15:5: error: no matching function for call to 'foo'
foo(1);
^~~
<source>:8:6: note: candidate template ignored: requirement 'std::is_base_of_v<Base, int>' was not satisfied [with T = int]
void foo(const T&) {
This, of course, is much more readable that some page-long, cryptic error message arising from an error from within the function caused by some operation that simply cannot be called on an int.
Using C++20 concepts, you could also write the template like so:
template<typename T>
requires std::is_base_of_v<Base, T>
void foo(const T&) {
}
Concepts greatly improve readability as template metaprogramming code can quickly get very lenghty and hard to read.
If you need to check more complex properties of a type, you can combine multiple type traits or write your own, which can get very complicated depending on what it is that you need to check.
I hope that clears things up a little bit for you!
Basic Problem Statement
I'm learning about SFINAE. I tried an extremely simple enable_if:
// 1: A foo() that accepts arguments that are derived from Base
template <typename T, typename Enable = enable_if_t<std::is_base_of_v<Base, T>>>
void foo(T thing) {
std::cout << "It's a derived!" << std::endl;
}
// 2: A foo() that accepts all other arguments
template <typename T, typename Enable = enable_if_t<!std::is_base_of_v<Base, T>>>
void foo(T thing) {
std::cout << "It's not a derived." << std::endl;
}
The compiler complains that foo is multiply defined. The internet tells me that this is because the template arguments aren't relevant when checking function signatures.
Variants I Tried
In my quest to do the most basic metaprogramming possible, I began throwing syntax at the problem. Here's a list of things that I tried for the enable_if statement (inverted statement i.e. !std::is_base_of identical, but omitted for brevity):
Anonymous Type, No typename, Equals 0
https://en.cppreference.com/w/cpp/types/enable_if tells me that what I did above was wrong. But its suggestion (found under the first notes block) is appropriately cryptic, and more importantly, also doesn't compile.
std::enable_if_t<std::is_base_of_v<Base, T>> = 0
Anonymous Type, No typename, Equals void
Thinking that maybe if I'm programming with types, using a type would be a wise choice, I instead tried to default the template to void. No dice.
std::enable_if_t<std::is_base_of_v<Base, T>> = void
Anonymous Type, Yes typename, Equals void
While we're throwing syntax at it, if I'm defaulting this template parameter to a type, shouldn't I use the typename keyword?
typename std::enable_if_t<std::is_base_of_v<Base, T>> = void
What Finally And Oh So Obviously Worked
typename enable_if_t<std::is_base_of_v<Base, T>, T>* = nullptr
I've asked everyone I know why this works yet my other variants don't, and they are equally confused. I'm at a loss. To make matters more confusing, if I name this type (e.g. typename Enable = ...), it fails to compile.
I would be extremely grateful if one who is more familiar with TMP and enable_if would explain to me:
Why does declaring the enable_if as a pointer to a type and defaulting it to nullptr work?
What are the semantic rules for defaulting enable_if?
What are the semantic rules for naming types produced by enable_if?
Is there a reference I can use which clearly explains this and other rules like it in template-land?
Many thanks.
The first set of variants you are just setting the value of a template type argument. Two overloads with different values for a template type argument collide, as they are both of kind template<class,class> and have the same function arguments.
The non-type template argument cases, the ones where you use a raw enable if you end up having a template non type argument of type void. That is illegal; the various error messages are the various ways it is illegal.
When you add a star, when the enable if clause passes it is a template non type argument of type void pointer.
When it fails, it isn't an argument at all.
An equivalent to the nullptr case is:
std::enable_if_t<std::is_base_of_v<Base, T>, bool> = true
when the clause is true, the enable if evaluates to bool, and we get:
bool = true
a template non-type argument of type bool that defaults to true. When the clause (the base of clause) is false, we get a SFINAE failure; there is no template type or non-type argument there.
With the class Whatever = enable_if cases we are trying SFINAE based on default value of template arguments. This leads to signature collision, because signatures have to be unique if they are found during overload resolution (in the same phase).
With the enable = value cases, we are trying SFINAE based on if there is a template non-type argument there. On failure, there is no signature to compare, so it cannot collide.
What remains is to make the syntax simple and pretty.
Now, this is all obsolete with Concepts, so don't fall in love with the syntax.
I'm following this great tutorial. The author heavily uses variadic templates and I came to a point where I'm stuck, can't understand. Can you help me?
1. Why isn't this compiling?
// this is simple
template<size_t I, typename T>
struct tuple_element
{
T value;
};
// this does NOT compiles: error: parameter pack 'Indices' must be at the end of the template parameter list
template <size_t... Indices, typename... Types>
struct tuple_impl : tuple_element<Indices, Types>...
{
};
Next, the author have this code that compiles fine:
template <size_t... Indices>
struct index_sequence
{
using type = index_sequence<Indices...>;
};
template <typename Sequence, typename... Types>
struct tuple_impl;
template <size_t... Indices, typename... Types>
struct tuple_impl<index_sequence<Indices...>, Types...>
: tuple_element<Indices, Types>...
{
};
2. Why in this case everything ok? I see almost the same pattern here: tuple_element<Indices, Types>...
3. Why this can't be compiled:
template <size_t... Indices>
void g(Indices...){}; //error: variable or field 'g' declared void
The error you are seeing is in the passing of parameters. This is a compile error:
template <size_t... Indices, typename... Types>
struct tuple_impl
{};
live example
The rule is you cannot have one pack followed by another in a template class template parameter list.
The second example is a specialization, where the pack rule does not exist. The template parameters of a specialization are merely the types and values which are extracted from the pattern matching against the primary templates, as guided by the <> portion of the specialization after the type name.
As they are never passed in in that same list & order, having one ... after another doesn't cause any ambiguity. With a primary template, the order matters, and anything after a ... is difficult to distinguish from more of the .... Probably to keep the compiler's job easier, even in cases where it cannot be ambiguous (say, a pack of literals followed by a pack of types), C++ bans its use.
The compiler would have no means to distinguish when the first sequence ends and the second starts - therefore it is allowed to have only one parameter pack in variadic templates, at the end.
tuple_impl<a, b, c, d, e, f> //is d still an index or already a type? what about e?
This works, because tuple_impl itself is a template that has itself only one parameter pack, the Types.... It just happes that in this specialization the first parameter is a template, too, which has a parameter pack, too. So, in contrast to one template with two packs, you have two templates with one pack each, which is ok.
This does not have to do with variadic templates, i.e. it would not work with a single argument either, for the same reason. The fact is, that since Indices... are not types but values, you are not defining a function. If it was not void, the compiler would have had problems later. Consider this example, which is slightly modified but essentially a similar construct:
template <size_t I>
unsigned g(I)
{}
The middle line is central: The compiler thinks that it is a variable definition, initialized with I. Therefore the error in your case, because variables of type void simply don't make sense. My compiler then emits a warning about the template, it thinks that g is a templated variable, and those are a C++1y extension. After that is done, he realizes the variable definition is not finished by a ;, emits an error and exits...
Just to add some important thing: this is a test snippet:
int main( int argc, char** argv )
{
using i3 = index_sequence<1, 2, 3>;
tuple_impl<i3, int, double, char> tup;
return 0;
}
Note: here you pass this i3 as the "index pack". The "master template" always defines how the parameters have to be passed to the template. The template<...> statement if set for specialization does not define anything, but just what internally the parameter combination may spread inside the specialization, but it's not a part of public interface.
For example, if you try to use <1, 2, 3, int, double, char> as a parameter specification (which would theoretically match the template parameter specification for the specialization), it will fail to compile.
I noted that much of boost and libc++/libstdc++ explicitly provide a default value of zero for SFINAE in code like
// libc++ http://llvm.org/svn/llvm-project/libcxx/trunk/include/memory
namespace __has_pointer_type_imp
{
template <class _Up> static __two __test(...);
template <class _Up> static char __test(typename _Up::pointer* = 0);
}
template <class _Tp>
struct __has_pointer_type
: public integral_constant<bool, sizeof(__has_pointer_type_imp::__test<_Tp>(0)) == 1>
{
};
However it confuses me as to why this would be expected when they explicitly make the call with 0. I remember hearing somewhere it was an optimization (to speed up the compiler when instantiating the template) but I don't fully understand how that would work. I looked at the standard and it has a section that briefly describes what happens with default-arguments in relation to template argument deduction.
14.8.2
At certain points in the template argument deduction process it is necessary to take a function type that makes use of template parameters and replace those template parameters with the corresponding template arguments. This is done at the beginning of template argument deduction when any explicitly specified template arguments are substituted into the function type, and again at the end of template argument deduction when any template arguments that were deduced or obtained from default arguments are substituted.
The last bit there sounds concerning to my question
and again at the end of template argument deduction when any template arguments that were deduced or obtained from default arguments are substituted.
However that sounds like the opposite of an optimization if it has to do more work. Does anyone have any reasoning why that 0 must be there, it works without it, but every single example of SFINAE in libc++ at least seems to explicitly put 0 there, even though they never call the function with no arguments.
You give a default value because you want to be able to not give that parameter, though the condition is still check !
Example:
template<typename T,
typename std::enable_if<
std::is_floating_point_v<T>
>::type = 0>
void foo() { std::cout << "1"; }
One can then call this function in the main with only one template parameter! Better, one does not even need to name the second parameter since it is unused.
A similar example when the parameter is given in the input of the function foo:
template<class T>
void foo(T t,
typename std::enable_if<
std::is_floating_point_v<T>
>::type = 0) { std::cout << "2"; }
then you could give a second parameter to the function foobut you do not want to do that in general.
I am struggling a bit with templates; I am trying to write a method that iterates over a range of strings, no matter their type or the container they are kept in. In the following code:
template<template<class> class ContainerType,
typename CharType>
ContainerType<basic_string<CharType>>
foo(typename ContainerType<basic_string<CharType>>::iterator begin,
typename ContainerType<basic_string<CharType>>::iterator end,
CharType letter)
{
return ContainerType<basic_string<CharType>>();
}
int main()
{
vector<string> words;
auto bar = foo(words.begin(), words.end(), 'a');
}
The compiler can't figure out the type of ContainerType.
I must say that I am a beginner when it comes to C++ templates.
Simply speaking, template argument type deduction only works to the right of the last ::, if there is one. Imagine what you're telling the compiler:
I am calling foo() with a certain type. Now I want you to look at all single-parameter class templates which could possibly exist, try to instantiate each of them with all possible types, and see for which of these a nested typedef iterator matches the type I sent to foo. Then use that combination as template arguments.
I believe it's pretty obvious that doesn't work. That's why anything to the left of :: is a non-deduced context, so template parameters in such context don't participate in template argument deduction. And since foo offers no other context, the argument cannot be deduced.