Extended copy constructor with custom allocators - c++

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

Related

Argument dependent lookup with stream operator for std::vector

I have created a stream output operator for std::vector in my namespace, and I was able to use that in my previous debug code:
namespace my_company {
template <typename T>
std::ostream &operator<<(std::ostream &os, std::vector<T> const &vec) {
os << "{";
for (int i = 0; i < vec.size(); ++i) {
if (i != 0) {
os << ", ";
}
os << vec[i];
}
os << "}";
return os;
}
template <typename T>
void debug_impl(T const &var, char const *const expr, char const *const file, int const line) {
using std::vector;
std::lock_guard lg(debug_print_mutex);
std::cout << "DEBUG " << file << ":" << line << " T" << std::this_thread::get_id() << "\n " << expr << ": "
<< var << std::endl;}
#define DEBUG(var) ::my_company::debug_impl(var, #var, __FILE__, __LINE__)
}
But now I want to use my logger class:
namespace my_company {
class Logger {
public:
virtual ~Logger() = default;
virtual void log_impl(LogLevel log_level, std::string message) = 0;
template <typename... T>
void log_bits(LogLevel log_level, T const &...ts) {
if (log_level >= threshold)
log_impl(log_level, format_bits(ts...));
}
template <typename... T>
void trace(T const &...ts) {
log_bits(LogLevel::trace, ts...);
}
private:
LogLevel threshold = LogLevel::trace;
};
}
The format function is this one:
namespace my_company {
template <typename... T>
std::string format_bits(T const &...ts) {
std::ostringstream oss;
// The following is a C++17 [fold expression](https://en.cppreference.com/w/cpp/language/fold).
(oss << ... << ts);
return oss.str();
}
}
In the debug_impl function I just delegate to the logger now:
namespace my_company {
template <typename T>
void debug_impl(T const &var, char const *const expr, char const *const file, int const line) {
logger->template trace(file, ":", line, " T", std::this_thread::get_id(), "\n ", expr, ": ", var);
}
}
Clang doesn't like that:
logger.h:29:11: error: call to function 'operator<<' that is neither visible in the template definition nor found by argument-dependent lookup
(oss << ... << ts);
^
logger.h:44:27: note: in instantiation of function template specialization 'my_company::format_bits<const char *, char [2], int, char [3], std::thread::id, char [4], const char *, char [3], std::vector<bool, std::allocator<bool>>>' requested here
log_impl(log_level, format_bits(ts...));
^
logger.h:69:5: note: in instantiation of function template specialization 'my_company::Logger::log_bits<const char *, char [2], int, char [3], std::thread::id, char [4], const char *, char [3], std::vector<bool, std::allocator<bool>>>' requested here
log_bits(LogLevel::trace, ts...);
^
util.h:693:20: note: in instantiation of function template specialization 'my_company::Logger::trace<const char *, char [2], int, char [3], std::thread::id, char [4], const char *, char [3], std::vector<bool, std::allocator<bool>>>' requested here
logger->template trace(file, ":", line, " T", std::this_thread::get_id(), "\n ", expr, ": ", var);
^
rotating_buffer.h:40:5: note: in instantiation of function template specialization 'my_company::debug_impl<std::vector<bool, std::allocator<bool>>>' requested here
DEBUG(used);
^
util.h:699:42: note: expanded from macro 'DEBUG'
#define DEBUG(var) ::my_company::debug_impl(var, #var, __FILE__, __LINE__)
^
logger.h:13:15: note: 'operator<<' should be declared prior to the call site
std::ostream &operator<<(std::ostream &os, std::vector<T> const &vec) {
^
I have already declared that prior to the call site, it is all declared in the logger.h file, which is included by the others. The operator<< is the first thing in that file, so it should be available. I presume that the additional layer of templates did not make it any easier.
I have read a few other questions, but I don't understand how I can solve this issue as putting the operator<< into std isn't allowed, but it doesn't work in my_company and putting it into the global namespace doesn't work either. What can I do?
We use Clang 11 and C++20 on Linux.
Somewhat related:
Argument Dependent Lookup and stream operators overloading
Output stream operator Argument Dependent Lookup (ADL) for fundamental/STL types/classes
It seems to be a clang 11 bug.
Clang 12 accept the code Demo.
As work around, you might use, instead of (oss << ... << ts);:
((oss << ts), ...);

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.

Compilation failure when inner class uses outer class template parameter but only when using `ostream`

I run into a compiler error when an inner class uses the template parameter of the outer class, and I instantiate an output stream operator on the outer class on the member of the inner type.
I have spent a good bit of time trying to resolve this problem. I believe the following sources come close but I still do not understand why I am getting a compilation failure.
https://isocpp.org/wiki/faq/templates#nondependent-name-lookup-types
https://msdn.microsoft.com/en-us/library/71dw8xzh.aspx
Here is the code:
#include <iostream>
#include <vector>
template <typename T>
struct Outer
{
struct Inner
{
Inner(const T& val = T());
T data_;
}; // end class Inner
Outer();
void AddInnerChildToOuter(const T& data);
std::vector<typename Outer<T>::Inner> innerChildren_;
}; // end class Outer
// Inner constructor
template <typename T>
Outer<T>::Inner::Inner(const T& val) : data_(val)
{
}
template <typename T>
std::ostream& operator<<(std::ostream& strm, // Line 27
const typename Outer<T>::Inner& gn)
{
strm << gn.data_ << std::endl;
return strm;
}
// Outer constructor
template <typename T>
Outer<T>::Outer()
{
}
template <typename T>
void Outer<T>::AddInnerChildToOuter(const T& data)
{
typename Outer<T>::Inner node(data);
innerChildren_.push_back(node);
}
template <typename T>
std::ostream& operator<<(std::ostream& strm, const Outer<T>& g)
{
for (size_t i = 0; i < g.innerChildren_.size(); ++i)
std::cout << g.innerChildren_[i] << std::endl; // Line 51
return strm;
}
int main()
{
Outer<int> g;
g.AddInnerChildToOuter(3);
g.AddInnerChildToOuter(5);
std::cout << g << std::endl; // Line 60
return 0;
}
I get a compiler error for the ostream operator << on the outer that calls the corresponding output stream operator for the inner. I am not posting the entire litany of the compiler's error messages; just what I consider to be relevant.
$ g++ -Wall -W -Wextra -pedantic -ansi OuterInnerArgh.cpp
OuterInnerArgh.cpp: In instantiation of ‘std::ostream& operator<<(std::ostream&, const Outer<T>&) [with T = int; std::ostream = std::basic_ostream<char>]’:
OuterInnerArgh.cpp:60:18: required from here
OuterInnerArgh.cpp:51:19: error: no match for ‘operator<<’ (operand types are ‘std::ostream {aka std::basic_ostream<char>}’ and ‘const Outer<int>::Inner’)
std::cout << g.innerChildren_[i] << std::endl;
~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~
(snipped compiler's attempts at various ostream overloads; more compiler error messages below)
OuterInnerArgh.cpp:27:15: note: candidate: template<class T> std::ostream& operator<<(std::ostream&, const typename Outer<T>::Inner&)
std::ostream& operator<<(std::ostream& strm,
^~~~~~~~
OuterInnerArgh.cpp:27:15: note: template argument deduction/substitution failed:
OuterInnerArgh.cpp:51:19: note: couldn't deduce template parameter ‘T’
std::cout << g.innerChildren_[i] << std::endl;
~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~
OuterInnerArgh.cpp:48:15: note: candidate: template<class T> std::ostream& operator<<(std::ostream&, const Outer<T>&)
std::ostream& operator<<(std::ostream& strm, const Outer<T>& g)
^~~~~~~~
OuterInnerArgh.cpp:48:15: note: template argument deduction/substitution failed:
OuterInnerArgh.cpp:51:19: note: ‘const Outer<int>::Inner’ is not derived from ‘const Outer<T>’
std::cout << g.innerChildren_[i] << std::endl;
~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~
(snipped rest of compiler errors)
Please let me know why I encounter a compiler error -
Even though I have an ostream operator << for typename Outer<T>::Inner&
Even when I have "sprinkled" the typename "magic-dust" wherever relevant
Only for the outer ostream operator, not for the constructors or the inner ostream operator (the latter probably not instantiated at all?)
Why does the compiler say ‘const Outer<int>::Inner’ is not derived from ‘const Outer<T>’? (Yes, there is no inheritance, but the inner type definition is nested within the outer)
You got compile error because non-deduced context causes template argument deduction failing.
In the following cases, the types, templates, and non-type values that are used to compose P do not participate in template argument deduction, but instead use the template arguments that were either deduced elsewhere or explicitly specified. If a template parameter is used only in non-deduced contexts and is not explicitly specified, template argument deduction fails.
The nested-name-specifier (everything to the left of the scope resolution operator ::) of a type that was specified using a qualified-id:
For example, if you specify the template argument explicitly (in ugly style) it would compile. In the operator<< for Outer:
operator<< <T> (strm, g.innerChildren_[i]);
// ^^^
You can make the operator<< non-template (to bypass the type deduction trouble), then you have to define it inside the class definition as friend. e.g.
struct Inner
{
Inner(const T& val = T());
T data_;
friend std::ostream& operator<<(std::ostream& strm,
const Inner& gn)
{
strm << gn.data_ << std::endl;
return strm;
}
};
LIVE
Fix this
std::cout << g.innerChildren_[i] << std::endl; // Line 51
to be
std::cout << g.innerChildren_[i].data_ << std::endl; // Line 51
Because you are trying to use the operator << where it is not defined
If you depend on the defined operator in line (27), modify line (50) to call it properly, as follows
for (size_t i = 0; i < g.innerChildren_.size(); ++i)
{
operator<< <T>(strm, g.innerChildren_[i]);
strm << std::endl;
}

Function template specialization in MacOSX with clang

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;
}

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.