Function template specialization in MacOSX with clang - c++

I am porting some codes from Windows to MacOSX. There is a problem about Function template specialization.
I was successfully compile this file in Windows with msvc14.0 and clang5.0.
#include <cstring>
#include <string>
#include <iostream>
template<typename CHAR_TYPE>
size_t string_length(CHAR_TYPE text)
{
static_assert(false, "string_length is not supported this type.");
return 0;
}
template<typename CHAR_TYPE>
size_t string_length(const std::basic_string<CHAR_TYPE>& text)
{
return text.size();
}
template<>
inline size_t string_length(const char* szText)
{
return strlen(szText);
}
template<>
inline size_t string_length(const wchar_t* szwText)
{
return wcslen(szwText);
}
int main()
{
std::cout << "SZ length " << string_length("IIII") << std::endl;
std::cout << "WSZ length " << string_length(L"IIII") << std::endl;
std::cout << "string length " << string_length(std::string("IIII")) << std::endl;
std::cout << "wstring length " << string_length(std::wstring(L"IIII")) << std::endl;
}
When I compile it in MacOSX with clang5.0, I got a error:
clang_test.cpp:9:5: error: static_assert failed "string_length is not supported
this type."
static_assert(false, "string_length is not supported this type.");
^ ~~~~~
1 error generated.
When I Remove the first function:
template<typename CHAR_TYPE>
size_t string_length(CHAR_TYPE text)
{
static_assert(false, "string_length is not supported this type.");
return 0;
}
I got some other errors:
clang_test.cpp:19:15: error: no function template matches function template
specialization 'string_length'
inline size_t string_length(const char* szText)
^
clang_test.cpp:13:8: note: candidate template ignored: could not match 'const
basic_string<type-parameter-0-0, char_traits<type-parameter-0-0>,
allocator<type-parameter-0-0> > &' against 'const char *'
size_t string_length(const std::basic_string<CHAR_TYPE>& text)
^
clang_test.cpp:25:15: error: no function template matches function template
specialization 'string_length'
inline size_t string_length(const wchar_t* szwText)
^
clang_test.cpp:13:8: note: candidate template ignored: could not match 'const
basic_string<type-parameter-0-0, char_traits<type-parameter-0-0>,
allocator<type-parameter-0-0> > &' against 'const wchar_t *'
size_t string_length(const std::basic_string<CHAR_TYPE>& text)
^
clang_test.cpp:33:34: error: no matching function for call to 'string_length'
std::cout << "SZ length " << string_length("IIII") << std::endl;
^~~~~~~~~~~~~
clang_test.cpp:13:8: note: candidate template ignored: could not match
'basic_string<type-parameter-0-0, char_traits<type-parameter-0-0>,
allocator<type-parameter-0-0> >' against 'char const[5]'
size_t string_length(const std::basic_string<CHAR_TYPE>& text)
^
clang_test.cpp:34:35: error: no matching function for call to 'string_length'
std::cout << "WSZ length " << string_length(L"IIII") << std::endl;
^~~~~~~~~~~~~
clang_test.cpp:13:8: note: candidate template ignored: could not match
'basic_string<type-parameter-0-0, char_traits<type-parameter-0-0>,
allocator<type-parameter-0-0> >' against 'wchar_t const[5]'
size_t string_length(const std::basic_string<CHAR_TYPE>& text)
^
4 errors generated.
What is the problem?

Following is indeed wrong even if function is not instantiated:
template<typename CHAR_TYPE>
size_t string_length(CHAR_TYPE text)
{
static_assert(false, "string_length is not supported this type.");
return 0;
}
temp.res/8:
The program is ill-formed, no diagnostic required, if:
If no valid specialization can be generated for a template definition, and that template is not instantiated, the template definition is ill-formed, no diagnostic required.
[..]
a hypothetical instantiation of a template immediately following its definition would be ill-formed due to a construct that does not depend on a template parameter, or
[..]
You may use instead (and use overload instead of specialization for const char*, const wchar_t*):
template<typename CHAR_TYPE>
size_t string_length(CHAR_TYPE text) = delete;
If you prefer to keep the static_assert, you have to create an helper:
template <typename T> struct always_false : false_type {};
template<typename CHAR_TYPE>
size_t string_length(CHAR_TYPE text)
{
static_assert(always_false<CHAR_TYPE>::value,
"string_length is not supported this type.");
return 0;
}

Related

Extended copy constructor with custom allocators

In my experiments with scoped_allocator_adaptors using x86_64 gcc/clang trunk, I've run into an issue where the code snippet below compiles if the extended copy constructor is enabled but without it results is_constructible static assertion below. The compiler complains of the extended copy constructor not being defined even though it is never invoked when enabled. Can someone help answer why that might be the case?
#include <iostream>
#include <vector>
#include <scoped_allocator>
template <typename T>
struct MyAlloc
{
using value_type = T;
MyAlloc(const std::string &scope) noexcept : _scope(scope) {}
// Rebinding allocatos to different type
template <class U> MyAlloc(MyAlloc<U> const& other) noexcept : _scope(other._scope) {}
value_type* allocate(std::size_t n) noexcept
{
std::cout << "Allocating " << n << " objects within " << _scope << " from " << __PRETTY_FUNCTION__ << std::endl;
return static_cast<value_type*>(::operator new (n*sizeof(value_type)));
}
void deallocate(value_type* p, std::size_t n) noexcept
{
std::cout << "Deallocating " << n << " objects within " << _scope << " from " << __PRETTY_FUNCTION__ << std::endl;
::operator delete(p);
}
std::string _scope;
};
template <typename T>
using MyAllocAdaptor = std::scoped_allocator_adaptor<MyAlloc<T>>;
template <typename T> // adaptor to propagate
using myvec = std::vector<T, MyAllocAdaptor<T>>;
template <typename T>
using bstr = std::basic_string<T, std::char_traits<T>, MyAlloc<T>>;
using mystring = bstr<char>;
// Example struct with multiple nested containers with different types
// More realistic example that single type containers
class S
{
int z;
mystring str;
myvec<int> vec;
public:
// If not public, the allocator aware constructor is not invoked
// limiting the propagation of the allocator
using allocator_type = MyAllocAdaptor<S>;
S(allocator_type alloc) :
z(1),
str("This string should really not have SBO....", std::allocator_traits<allocator_type>::rebind_alloc<char>(alloc)),
vec(std::allocator_traits<allocator_type>::rebind_alloc<int>(alloc))
{
vec.push_back(10);
}
S() : S(allocator_type("NoScope")) {
std::cout << "Should not be invoked " << __PRETTY_FUNCTION__ << std::endl;
}
~S()
{
std::cout << __PRETTY_FUNCTION__ << " Z " << z << std::endl;
}
#if 0 // If this is defined things compile
S(const S& other, allocator_type alloc = {}) :
str(other.str, std::allocator_traits<allocator_type>::rebind_alloc<char>(alloc)),
vec(other.vec, std::allocator_traits<allocator_type>::rebind_alloc<int>(alloc))
{
}
#endif
};
int main()
{
MyAlloc<S> alloc("scope1");
myvec<S> vec(alloc);
vec.emplace_back();
}
Compiler error when the extended copy constructor is not defined (even though it's never invoked when defined)
In file included from <source>:3:
In file included from /opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/11.0.0/../../../../include/c++/11.0.0/scoped_allocator:39:
In file included from /opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/11.0.0/../../../../include/c++/11.0.0/tuple:40:
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/11.0.0/../../../../include/c++/11.0.0/bits/uses_allocator.h:96:7: error: static_assert failed due to requirement '__or_<std::is_constructible<S, std::allocator_arg_t, const std::scoped_allocator_adaptor<MyAlloc<S>> &, const S &>, std::is_constructible<S, const S &, const std::scoped_allocator_adaptor<MyAlloc<S>> &>>::value' "construction with an allocator must be possible if uses_allocator is true"
static_assert(__or_<
^ ~~~~~~
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/11.0.0/../../../../include/c++/11.0.0/scoped_allocator:377:8: note: in instantiation of template class 'std::__uses_alloc<true, S, std::scoped_allocator_adaptor<MyAlloc<S>>, const S &>' requested here
= std::__use_alloc<_Tp, inner_allocator_type, _Args...>(__inner);
^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/11.0.0/../../../../include/c++/11.0.0/bits/alloc_traits.h:247:8: note: in instantiation of function template specialization 'std::scoped_allocator_adaptor<MyAlloc<S>>::construct<S, const S &>' requested here
{ __a.construct(__p, std::forward<_Args>(__args)...); }
^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/11.0.0/../../../../include/c++/11.0.0/bits/stl_uninitialized.h:318:16: note: in instantiation of function template specialization 'std::allocator_traits<std::scoped_allocator_adaptor<MyAlloc<S>>>::construct<S, const S &>' requested here
__traits::construct(__alloc, std::__addressof(*__cur), *__first);
^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/11.0.0/../../../../include/c++/11.0.0/bits/stl_uninitialized.h:353:19: note: in instantiation of function template specialization 'std::__uninitialized_copy_a<const S *, S *, std::scoped_allocator_adaptor<MyAlloc<S>>>' requested here
return std::__uninitialized_copy_a
^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/11.0.0/../../../../include/c++/11.0.0/bits/vector.tcc:473:10: note: in instantiation of function template specialization 'std::__uninitialized_move_if_noexcept_a<S *, S *, std::scoped_allocator_adaptor<MyAlloc<S>>>' requested here
= std::__uninitialized_move_if_noexcept_a
^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/11.0.0/../../../../include/c++/11.0.0/bits/vector.tcc:121:4: note: in instantiation of function template specialization 'std::vector<S, std::scoped_allocator_adaptor<MyAlloc<S>>>::_M_realloc_insert<>' requested here
_M_realloc_insert(end(), std::forward<_Args>(__args)...);
^
<source>:78:9: note: in instantiation of function template specialization 'std::vector<S, std::scoped_allocator_adaptor<MyAlloc<S>>>::emplace_back<>' requested here
vec.emplace_back();
^
1 error generated.
Compiler returned: 1

scoped_allocator_adaptor seems to require allocator to be default constructed

In my experiments with scoped_allocator_adaptor, I'm trying to pass the allocator obtained from main(..) into S1's constructor (more generally there would be multiple different types within S1 that would all use the allocator that was made available in the constructor). However, I get the compile error below indicating that the allocator should be default constructible. Can someone help explain why this might be the case? Is there some conversion taking place leading to the default constructed version of the allocator being needed?
#include <iostream>
#include <cassert>
#include <vector>
#include <scoped_allocator>
// Move allocator and container aliases into namepsace
namespace custom
{
template <typename T>
struct MyAlloc
{
using value_type = T;
MyAlloc(const std::string &scope) noexcept : _scope(scope) {}
// Rebinding allocatos to different type
template <class U>
MyAlloc(const MyAlloc<U> & other) noexcept : _scope(other._scope) {}
// Allow for move operations to be noexcept
//using is_always_equal = std::true_type;
value_type* allocate(std::size_t n) noexcept
{
std::cout << "Allocating " << n << " objects within " << _scope << " from " << __PRETTY_FUNCTION__ << std::endl;
return static_cast<value_type*>(::operator new (n*sizeof(value_type)));
}
void deallocate(value_type* p, std::size_t n) noexcept
{
std::cout << "Deallocating " << n << " objects within " << _scope << " from " << __PRETTY_FUNCTION__ << std::endl;
::operator delete(p);
}
std::string _scope;
};
// Allocators compare equal to enable one allocator to de-allocate memory
// from another
template <typename T>
bool operator==(const MyAlloc<T> &x1, const MyAlloc<T> &x2) noexcept
{
return true;
}
template <typename T>
bool operator!=(const MyAlloc<T> &x1, const MyAlloc<T> &x2) noexcept
{
return !(x1 == x2);
}
template <typename T>
using allocator = std::scoped_allocator_adaptor<MyAlloc<T>>;
template <typename T> // adaptor to propagate
using vector = std::vector<T, allocator<T>>;
template <typename T>
using bstr = std::basic_string<T, std::char_traits<T>, allocator<T>>;
using string = bstr<char>;
}
struct S1
{
using allocator_type = custom::allocator<std::byte>;
S1(allocator_type alloc) : str("This is a very long string indeed..", std::allocator_traits<allocator_type>::rebind_alloc<char>(alloc))
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
S1(const S1 &other, allocator_type al) : str(other.str, std::allocator_traits<allocator_type>::rebind_alloc<char>(al))
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
custom::string str;
};
int main()
{
custom::allocator<std::byte> sc{"scope"};
custom::vector<S1> cv{sc};
// cv.emplace_back();
}
Compile error:
/usr/include/c++/10/scoped_allocator: In instantiation of ‘std::scoped_allocator_adaptor<_OuterAlloc, _InnerAllocs>::scoped_allocator_adaptor() [with _OuterAlloc = custom::MyAlloc<S1>; _InnerAllocs = {}]’:
/usr/include/c++/10/bits/stl_vector.h:626:35: required from here
/usr/include/c++/10/scoped_allocator:304:60: error: no matching function for call to ‘custom::MyAlloc<S1>::MyAlloc()’
304 | scoped_allocator_adaptor() : _OuterAlloc(), _M_inner() { }
| ^
d2.cc:17:9: note: candidate: ‘template<class U> custom::MyAlloc<T>::MyAlloc(const custom::MyAlloc<U>&) [with U = U; T = S1]’
17 | MyAlloc(const MyAlloc<U> & other) noexcept : _scope(other._scope) {}
| ^~~~~~~
d2.cc:17:9: note: template argument deduction/substitution failed:
In file included from d2.cc:4:
/usr/include/c++/10/scoped_allocator:304:60: note: candidate expects 1 argument, 0 provided
304 | scoped_allocator_adaptor() : _OuterAlloc(), _M_inner() { }
| ^
d2.cc:13:9: note: candidate: ‘custom::MyAlloc<T>::MyAlloc(const string&) [with T = S1; std::string = std::__cxx11::basic_string<char>]’
13 | MyAlloc(const std::string &scope) noexcept : _scope(scope) {}
| ^~~~~~~
d2.cc:13:9: note: candidate expects 1 argument, 0 provided
d2.cc:10:12: note: candidate: ‘custom::MyAlloc<S1>::MyAlloc(const custom::MyAlloc<S1>&)’
10 | struct MyAlloc
| ^~~~~~~
d2.cc:10:12: note: candidate expects 1 argument, 0 provided
d2.cc:10:12: note: candidate: ‘custom::MyAlloc<S1>::MyAlloc(custom::MyAlloc<S1>&&)’
d2.cc:10:12: note: candidate expects 1 argument, 0 provided
This line: custom::vector<S1> cv{sc}; is the problem.
Because you've used brackets, it's trying to call vectors initializer-list constructor, which has an optional parameter which is an allocator - which it default constructs.
See the last two constructors in the list on cppreference
If you change that line to custom::vector<S1> cv(sc); it will compile w/o error.

lambda converted to bool instead of deducing function-pointer-type

I wanted to implement a overload for operator<< that allowed me to call a given function and output the result.
I therefore wrote an overload, but the conversion to bool is selected and when writing a function myself, it would not compile.
EDIT: Know that I do not want to call the lambda,
but instead pass it to the function where it should be called with a default constructed parameter list.
I have appended my code:
#include <iostream>
template<typename T>
void test(T *) {
std::cout << "ptr" << std::endl;
}
template<typename T>
void test(bool) {
std::cout << "bool" << std::endl;
}
template<typename Ret, typename ...Args>
void test(Ret(*el)(Args...)) {
std::cout << "function ptr\n" << el(Args()...) << std::endl;
}
template<typename Char_T, typename Char_Traits, typename Ret, typename ...Args>
std::basic_ostream<Char_T, Char_Traits>& operator<<(
std::basic_ostream<Char_T, Char_Traits> &str, Ret(*el)(Args...)) {
return str << el(Args()...);
}
int main() {
std::boolalpha(std::cout);
std::cout << []{return 5;} << std::endl; // true is outputted
test([]{return 5;}); // will not compile
}
I use gcc 7.3.1 with the version flag -std=c++14.
EDIT: Error message:
main.cc: In function ‘int main()’:
main.cc:25:23: error: no matching function for call to ‘test(main()::<lambda()>)’
test([]{return 5;});
^
main.cc:5:6: note: candidate: template<class T> void test(T*)
void test(T *) {
^~~~
main.cc:5:6: note: template argument deduction/substitution failed:
main.cc:25:23: note: mismatched types ‘T*’ and ‘main()::<lambda()>’
test([]{return 5;});
^
main.cc:9:6: note: candidate: template<class T> void test(bool)
void test(bool) {
^~~~
main.cc:9:6: note: template argument deduction/substitution failed:
main.cc:25:23: note: couldn't deduce template parameter ‘T’
test([]{return 5;});
^
main.cc:13:6: note: candidate: template<class Ret, class ... Args> void test(Ret (*)(Args ...))
void test(Ret(*el)(Args...)) {
^~~~
main.cc:13:6: note: template argument deduction/substitution failed:
main.cc:25:23: note: mismatched types ‘Ret (*)(Args ...)’ and ‘main()::<lambda()>’
test([]{return 5;});
Your problem here is that Template Argument Deduction is only done on the actual argument passed to test. It's not done on all possible types that the argument could possibly converted to. That might be an infinite set, so that's clearly a no-go.
So, Template Argument Deduction is done on the actual lambda object, which has an unspeakable class type. So the deduction for test(T*) fails as the lambda object is not a pointer. T can't be deduced from test(bool), obviously. Finally, the deduction fails for test(Ret(*el)(Args...)) as the lambda object is not a pointer-to-function either.
There are a few options. You might not even need a template, you could accept a std::function<void(void)> and rely on the fact that it has a templated constructor. Or you could just take a test(T t) argument and call it as t(). T will now deduce to the actual lambda type. The most fancy solution is probably using std::invoke, and accepting a template vararg list.
Even though non-capturing lambdas have an implicit conversion to function pointers, function templates must match exactly for deduction to succeed, no conversions will be performed.
Therefore the easiest fix is to force the conversion with a +
int main() {
std::boolalpha(std::cout);
std::cout << []{return 5;} << std::endl; // true is outputted
test(+[]{return 5;});
// ^
}
template<typename T>
void test(bool) {
std::cout << "bool" << std::endl;
}
Template is not needed. In fact you overload functions, not templates. Replace it with
void test(bool) {
std::cout << "bool" << std::endl;
}
Now your sample will compile.

auto-returning functions and template instantiation

While writing some template code, I ran into <unresolved overloaded function type> errors which can be reduced to the following.
template <int N>
auto bar()
{
return N;
}
int main(int, char* [])
{
auto foo = [] (auto func) {
return func();
};
foo(bar<3>);
}
With the errors being:
unresolved_overload.cpp: In function 'int main(int, char**)':
unresolved_overload.cpp:26:28: error: no match for call to '(main(int, char**)::<lambda(auto:1)>) (<unresolved overloaded function type>)'
std::cout << foo(bar<3>) << std::endl;
^
unresolved_overload.cpp:21:29: note: candidate: template<class auto:1> constexpr main(int, char**)::<lambda(auto:1)>::operator decltype (((const main(int, char**)::<lambda(auto:1)>*)((const main(int, char**)::<lambda(auto:1)>* const)0))->operator()(static_cast<auto:1&&>(<anonymous>))) (*)(auto:1)() const
auto foo = [] (auto func) {
^
unresolved_overload.cpp:21:29: note: template argument deduction/substitution failed:
unresolved_overload.cpp:26:28: note: couldn't deduce template parameter 'auto:1'
std::cout << foo(bar<3>) << std::endl;
^
unresolved_overload.cpp:21:29: note: candidate: template<class auto:1> main(int, char**)::<lambda(auto:1)>
auto foo = [] (auto func) {
^
unresolved_overload.cpp:21:29: note: template argument deduction/substitution failed:
unresolved_overload.cpp:26:28: note: couldn't deduce template parameter 'auto:1'
std::cout << foo(bar<3>) << std::endl;
If we replace the auto-return with the explicit return type, int, the example will compile fine.
Why does auto-return run into these issues? I looked into template argument deduction and substitution but the search was largely unfruitful. I thought it might have something to do with the order of template instantiation / etc but couldn't make too much sense of it...
Per AndyG's suggestion, I found the same issue on GCC's bug list. Bug 64194. First reported in 2014. Thus the conclusion seems to be that this is a GCC bug and thankfully not another special case for templates.
Working around this just requires having something else to trigger the instantiation (e.g. assign to a variable, a using declaration).
Try this:
template <typename func>
auto bar(func&& f)->decltype(f())
{
return f();
}
int main()
{
int i = 100;
auto f = [=]()
{
return i;
};
bar(f);
return 0;
}

std::get<N> only accept const expression, how to bypass the restriction? [duplicate]

How do I use a variable to index into a tuple using std::get<>? I have the following code:
#include <iostream>
#include <tuple>
using namespace std;
int main() {
tuple<int, int> data(5, 10);
for (int i=0; i<2; i++) {
cout << "#" << i+1 << ":" << get<i>(data) << endl;
}
return 0;
}
and it fails with the following compiler error:
prog.cpp: In function 'int main()':
prog.cpp:10:39: error: the value of 'i' is not usable in a constant expression
cout << "#" << i+1 << ":" << get<i>(data) << endl;
^
prog.cpp:9:11: note: 'int i' is not const
for (int i=0; i<2; i++) {
^
prog.cpp:10:46: error: no matching function for call to
'get(std::tuple<int, int>&)'
cout << "#" << i+1 << ":" << get<i>(data) << endl;
^
prog.cpp:10:46: note: candidates are:
In file included from /usr/include/c++/4.9/tuple:38:0,
from prog.cpp:2:
/usr/include/c++/4.9/utility:143:5: note: template<unsigned int _Int,
class _Tp1, class _Tp2> constexpr typename std::tuple_element<_Int,
std::pair<_Tp1, _Tp2> >::type& std::get(std::pair<_Tp1, _Tp2>&)
get(std::pair<_Tp1, _Tp2>& __in) noexcept
^
/usr/include/c++/4.9/utility:143:5: note: template argument
deduction/substitution failed:
prog.cpp:10:46: error: the value of 'i' is not usable in a constant
expression
cout << "#" << i+1 << ":" << get<i>(data) << endl;
^
prog.cpp:9:11: note: 'int i' is not const
for (int i=0; i<2; i++) {
^
prog.cpp:10:46: note: in template argument for type 'unsigned int'
cout << "#" << i+1 << ":" << get<i>(data) << endl;
^
In file included from /usr/include/c++/4.9/tuple:38:0,
from prog.cpp:2:
/usr/include/c++/4.9/utility:148:5: note: template<unsigned int _Int,
class _Tp1, class _Tp2> constexpr typename std::tuple_element<_Int,
std::pair<_Tp1, _Tp2> >::type&& std::get(std::pair<_Tp1, _Tp2>&&)
get(std::pair<_Tp1, _Tp2>&& __in) noexcept
^
/usr/include/c++/4.9/utility:148:5: note: template argument
deduction/substitution failed:
prog.cpp:10:46: error: the value of 'i' is not usable in a constant
expression
cout << "#" << i+1 << ":" << get<i>(data) << endl;
^
prog.cpp:9:11: note: 'int i' is not const
for (int i=0; i<2; i++) {
^
prog.cpp:10:46: note: in template argument for type 'unsigned int'
cout << "#" << i+1 << ":" << get<i>(data) << endl;
^
In file included from /usr/include/c++/4.9/tuple:38:0,
from prog.cpp:2:
/usr/include/c++/4.9/utility:153:5: note: template<unsigned int _Int,
class _Tp1, class _Tp2> constexpr const typename
std::tuple_element<_Int, std::pair<_Tp1, _Tp2> >::type& std::get(const
std::pair<_Tp1, _Tp2>&)
get(const std::pair<_Tp1, _Tp2>& __in) noexcept
^
/usr/include/c++/4.9/utility:153:5: note: template argument
deduction/substitution failed:
prog.cpp:10:46: error: the value of 'i' is not usable in a constant
expression
cout << "#" << i+1 << ":" << get<i>(data) << endl;
^
prog.cpp:9:11: note: 'int i' is not const
for (int i=0; i<2; i++) {
^
prog.cpp:10:46: note: in template argument for type 'unsigned int'
cout << "#" << i+1 << ":" << get<i>(data) << endl;
^
In file included from /usr/include/c++/4.9/tuple:38:0,
from prog.cpp:2:
/usr/include/c++/4.9/utility:162:5: note: template<class _Tp, class
_Up> constexpr _Tp& std::get(std::pair<_T1, _T2>&)
get(pair<_Tp, _Up>& __p) noexcept
I actually truncated the compiler error message as I think it does not add much beyond thid point. Any idea how to make that work?
Edit:
Just to clarify, using an array type is not really an option. I have to use the tuple cause it is the return type of an API from a third party library. The example above is just to make it easy to understand.
How do I use a variable to index into a tuple using std::get<>?
You do not, std::get<> parameter value must be known at compile time.
Any idea how to make that work?
yes, use proper type:
int main() {
std::array<int, 2> data{ 5, 10 };
for (int i=0; i<2; i++) {
cout << "#" << i+1 << ":" << data[i] << endl;
}
return 0;
}
Any idea how to make that work?
Option 1
Use compile time constants to access the std::tuple.
cout << "#" << 1 << ":" << get<0>(data) << endl;
cout << "#" << 2 << ":" << get<1>(data) << endl;
Option 2
Use a container type whose elements can be accessed using an index at run time.
std::vector<int> data{5, 10};
or
std::array<int, 2> data{5, 10};
The likely answer that you should adopt is to just use an array, vector, or other kind of indexed container.
In the event that the tuple elements are not homogeneous types and you actually do need an answer for that case, it's a bit complex. This is because types need to be known at compile time. So where you think you could do std::cout << get_from_tuple(a_tuple, index) for example, this can't work as easily as you think it can because the operator<< overload for sending the object to the standard output stream is selected at compile time. Obviously, this means that the index must be known at compile time, too -- otherwise we can't know the type of the tuple element.
However, it is possible to build a template function that can, in fact, implement exactly this behavior. The end result, when compiled, is a conditional tree that is capable of handling each element in the tuple, but we enlist the compiler to help us build that conditional tree.
What I will build here is a function that, given a tuple, index, and functor, will invoke the functor, forwarding that particular tuple item, and will then return true. If the index is out of range, it will return false.
If the functor is not able to be called with every element in the tuple, the template function will fail to instantiate.
The final solution looks like this:
#include <tuple>
#include <type_traits>
namespace detail {
template <std::size_t I>
struct size_wrapper { };
template <typename V, typename Tup, std::size_t I>
bool visit_tuple_index_impl(Tup && t, std::size_t index, V && visitor, size_wrapper<I>)
{
if (index == I - 1) {
visitor(std::get<I - 1>(std::forward<Tup>(t)));
return true;
}
return visit_tuple_index_impl(std::forward<Tup>(t), index, visitor, size_wrapper<I - 1>());
}
template <typename V, typename Tup>
bool visit_tuple_index_impl(Tup &&, std::size_t, V &&, size_wrapper<0>)
{
return false;
}
}
template <typename V, typename Tup>
bool visit_tuple_index(Tup && t, std::size_t index, V && visitor)
{
return detail::visit_tuple_index_impl(
std::forward<Tup>(t),
index,
std::forward<V>(visitor),
detail::size_wrapper<std::tuple_size<typename std::decay<Tup>::type>::value>()
);
}
#include <utility>
template<std::size_t...Is>
auto index_over( std::index_sequence<Is...> ) {
return [](auto&& f)->decltype(auto){
return decltype(f)(f)( std::integral_constant<std::size_t, Is>{}... );
};
}
template<std::size_t N>
auto index_upto( std::integral_constant<std::size_t, N> ={} ) {
return index_over( std::make_index_sequence<N>{} );
}
template<class F>
auto foreacher( F&& f ) {
return [f=std::forward<F>(f)](auto&&...args)mutable {
(void(), ..., void(f(decltype(args)(args))));
};
}
template<std::size_t N>
auto count_upto( std::integral_constant<std::size_t, N> ={} ) {
return [](auto&& f){
index_upto<N>()(foreacher(decltype(f)(f)));
};
}
you can just do:
#include <iostream>
#include <tuple>
int main() {
std::tuple<int, int> data(5, 10);
count_upto<2>()([&](auto I){
std::cout << "#" << (I+1) << ":" << std::get<I>(data) << "\n";
});
}
Live example.
There is no unbounded recursion in this solution. It does require C++1z -- you can replace the body of foreacher with the using unused=int[]; trick in C++14.