I am trying to access variables in a struct thru nested member pointers:
#include <iostream>
typedef struct {
int a;
int b;
} bar;
typedef struct {
int c;
bar d;
} baz;
template <typename obj, class C1, class C2, typename T1, typename T2>
T2 test1(const obj& obj_, T1 C1::* field1_, T2 C2::* field2_)
{
return (obj_.*field1_).*field2_;
}
int main()
{
baz myObj;
test1(myObj, &baz::d, &bar::b);
}
How would I turn the function test into a variadic function, so that i can access variables at variable "depths" into the struct?
I've tried to follow the second example in the Function parameter list section here, but am not getting it it seems:
template <typename obj, class ...C, typename... T>
void test2(const obj& obj_, T C...::* field_)
{
// ??
// and what about the function return parameter?
}
int main()
{
baz myObj;
test2(obj,&baz::d,&bar::b);
test2(obj,&baz::c);
}
With this, the definition of test2() already doesn't compile.
Any (latest) version of C++ can be used (with MSVC though).
For tests purpose, here is a complete program on coliru.
Solution
Thanks to Silvio's answer, I was able to solve it. Taking advantage of C++17, it can be made slightly shorter still:
template <typename T, typename S, typename... Ss>
auto inline test2(const T& obj, S field1, Ss... fields)
{
if constexpr (!sizeof...(fields))
return obj.*field1;
else
return test2(obj.*field1, fields...);
}
There may be a cleaner way to do this, but you can certainly take the "throw it at the wall and see what sticks" approach that C++ templates love so much.
template <typename T>
auto test2(const T& obj) -> T {
return obj;
}
template <typename T, typename S, typename... Ss>
auto test2(const T& obj, S field1, Ss... fields)
-> decltype(test2(obj.*field1, fields...)) {
return test2(obj.*field1, fields...);
}
The base case is pretty straightforward. If we don't pass any fields, we just return the original object itself. The recursive case is just that: we recurse. The return type is declared to be... the declared type of the return value. The argument types are simply variables. They'll be instantiated fully as needed. If you pass arguments that don't make sense or don't type check, you'll get some wonderfully ugly error messages.
This requires c++17 support for folding expressions.
namespace utils {
template<class T>struct tag_t{ using type=T; };
template<class...Ts>
using last = typename std::tuple_element_t< sizeof...(Ts)-1, std::tuple<tag_t<Ts>...> >::type;
template<class Lhs, class F>
struct fold_invoker_t;
template<class Lhs, class F>
fold_invoker_t<Lhs, F> fold_invoker(Lhs&&lhs, F&& f);
template<class Lhs, class F>
struct fold_invoker_t {
Lhs lhs;
F f;
template<class Rhs>
auto operator*( Rhs&& rhs )&& {
return fold_invoker(std::forward<F>(f)(std::forward<Lhs>(lhs), std::forward<Rhs>(rhs)), static_cast<F>(f));
}
};
template<class Lhs, class F>
fold_invoker_t<Lhs, F> fold_invoker(Lhs&&lhs, F&& f){ return {std::forward<Lhs>(lhs), std::forward<F>(f)}; }
}
then we write:
template <typename Obj, class ...C, typename... T>
utils::last<Obj, T...> const& test2(const Obj& obj, T C::*... field)
{
auto get_member=[](auto&& elem, auto&& memptr)->decltype(auto){ return elem.*memptr; };
return (utils::fold_invoker( obj, get_member ) * ... * field).lhs;
}
and it is all expanded on that one line.
live example.
No idea if this will work in MSVC's C++17 support.
Related
I am trying to achieve the following:
template<template<typename> bool Function_, typename ... Types_>
constexpr auto find(Tuple<Types_ ... >) noexcept
{
// ...
}
where a possible function could be:
template<typename T>
inline constexpr bool is_pointer_v = is_pointer<T>::value;
so then the usage of find would be:
Tuple<int, char, void *> t;
find<is_pointer_v>(t);
don't worry about the implementation of find, I am just asking about how to do "template < typename > bool Function_" as the bool part is invalid in c++ currently.
any help is appreciated!
EDIT:
here is an example of why I can't pass the "is_pointer" to the function:
template<typename T_>
constexpr auto add_pointer(Type<T_>) noexcept
{ return type_c<T_ *>; }
template<typename F_, typename T_>
constexpr auto apply(F_ f, Type<T_> t) noexcept
{
return f(t);
}
int main(void)
{
Type<int> t_i;
apply(add_pointer, t_i);
}
this produces the compiler error:
error: no matching function for call to ‘apply(< unresolved overloaded function type >, sigma::meta::Type&)’
apply(add_pointer, t_i);
any help is appreciated!
You can simply wrap your functions within functors.
As a minimal, working example:
template<typename>
struct Type {};
template<typename>
struct type_c {};
template<typename T_>
struct add_pointer {
static constexpr auto invoke(Type<T_>) noexcept
{ return type_c<T_ *>{}; }
};
template<template<typename> class F_, typename T_>
constexpr auto apply(Type<T_> t) noexcept {
return F_<T_>::invoke(t);
}
int main(void) {
Type<int> t_i;
apply<add_pointer>(t_i);
}
If you can't change them directly, create functors that forward everything to the right function through a static constexpr member method.
I am just asking about how to do "template < typename > bool Function_" as the bool part is invalid in c++ currently.
As far I know, template-template arguments are a completely different thing. They are intended for containers, not for functions. So class, not bool.
here is an example of why I can't pass the "is_pointer" to the function
Your example doesn't work because add_pointer is a template function, so when you call
apply(add_pointer, t_i);
the compiler doesn't know which version (which type T) of add_pointer to use.
A solution can be explicit it, as in the following simplified example
#include <tuple>
#include <iostream>
template <typename T>
constexpr auto add_pointer(std::tuple<T>) noexcept
{ std::cout << "add_pointer" << std::endl; return 0; }
template <typename F, typename T>
constexpr auto apply(F f, std::tuple<T> t) noexcept
{ return f(t); }
int main(void)
{
std::tuple<int> t_i { 1 };
apply<int(*)(std::tuple<int>)>(add_pointer, t_i);
}
but I understand that explicating int(*)(std::tuple<int>) is a big pain in the ass.
You can simplify a little using the fact that you pass t so you can deduce the type of the argument received by the function, but (for a generic solution) I don't know how to avoid to explicit the return type of the function (maybe it's possible, but (in this moment) I don't know.
So you can simplify the call as follows
apply<int>(add_pointer, t_i);
and the following is a little more general example
#include <tuple>
#include <iostream>
template <typename ... Ts>
constexpr auto add_pointer(std::tuple<Ts...> const &) noexcept
{ std::cout << "add_pointer" << std::endl; return 0; }
template <typename R, typename ... Ts,
typename F = R(*)(std::tuple<Ts...> const &)>
constexpr auto apply(F f, std::tuple<Ts...> t) noexcept
{ return f(t); }
int main(void)
{
std::tuple<int> t_i { 1 };
apply<int>(add_pointer, t_i);
}
I am writing a library where the user provides a callback as a lambda. In the default scenario I want to just call the lambda and pass the back an object.
Now there are non trivial senarios where the user may want the context as well. So I want to be able to use the same callback mechanism and just allow the user to add a context as a parameter to their lambda and I will then pass both the object and the context.
I can't quite get SFINAE to work.
I have simplified the code to this:
#include <string>
#include <iostream>
class Context {};
template<typename F>
struct UseContext
{
// I want to set this value to 0 or 1 based on the parameters
// in F but can't quite get this to work.
enum {value = 0 };
};
template<typename F, typename T, bool useContext = UseContext<F>::value>
struct Caller;
template<typename F, typename T>
struct Caller<F, T, true>
{
void operator()(F& func, Context& context, T& object)
{
func(context, object);
}
};
template<typename F, typename T>
struct Caller<F, T, false>
{
void operator()(F& func, Context&, T& object)
{
func(object);
}
};
template<typename T, typename F>
void doWork(F&& func)
{
Context context;
T object;
/// STUFF
Caller<F,T> caller;
caller(func, context, object);
}
Usage:
int main()
{
// if UseContext::value == 0 then this compiles.
// This is the normal situation.
doWork<std::string>([](std::string const& x){ std::cout << x << "\n";});
// if UseContext::value == 1 then this compiles.
// This is if the user wants more context about the work.
// most of the time this extra parameter is not required.
// So I don't want to force the user to add it to the parameter
// list of the lambda.
doWork<std::string>([](Context&, std::string const& x){ std::cout << x << "\n";});
}
Or if there is a better way of doing this.
Expression SFINAE:
template<class F, class T>
auto call(F& func, Context& context, T& object) -> decltype(func(context, object), void())
{
func(context, object);
}
template<class F, class T>
auto call(F& func, Context&, T& object) -> decltype(func(object), void())
{
func(object);
}
Then just call(func, context, object). This is ambiguous if both forms are valid. If you want to disambiguate, just add a dummy parameter and do the usual int/long trick.
My solution is to use std::is_constructible plus std::enable_if:
template<typename F,typename T>
typename std::enable_if<std::is_constructible<std::function<void(T const&)>,F>::value>:type doWork(F func)
{
//...
}
template<typename F,typename T>
typename std::enable_if<std::is_constructible<std::function<void(Context&,T const&)>,F>::value>:type doWork(F func)
{
//...
}
explenation - each std::function can be built from the equivilant lambda. here we are testing using std::enable_if if you can build a std::function<void(T)> or a std::function<void(Context,T)> and re-wire the correct function in compile time.
I want to write a simple adder (for giggles) that adds up every argument and returns a sum with appropriate type.
Currently, I've got this:
#include <iostream>
using namespace std;
template <class T>
T sum(const T& in)
{
return in;
}
template <class T, class... P>
auto sum(const T& t, const P&... p) -> decltype(t + sum(p...))
{
return t + sum(p...);
}
int main()
{
cout << sum(5, 10.0, 22.2) << endl;
}
On GCC 4.5.1 this seems to work just fine for 2 arguments e.g. sum(2, 5.5) returns with 7.5. However, with more arguments than this, I get errors that sum() is simply not defined yet. If I declare sum() like this however:
template <class T, class P...>
T sum(const T& t, const P&... p);
Then it works for any number of arguments, but sum(2, 5.5) would return integer 7, which is not what I would expect.
With more than two arguments I assume that decltype() would have to do some sort of recursion to be able to deduce the type of t + sum(p...). Is this legal C++0x? or does decltype() only work with non-variadic declarations? If that is the case, how would you write such a function?
I think the problem is that the variadic function template is only considered declared after you specified its return type so that sum in decltype can never refer to the variadic function template itself. But I'm not sure whether this is a GCC bug or C++0x simply doesn't allow this. My guess is that C++0x doesn't allow a "recursive" call in the ->decltype(expr) part.
As a workaround we can avoid this "recursive" call in ->decltype(expr) with a custom traits class:
#include <iostream>
#include <type_traits>
using namespace std;
template<class T> typename std::add_rvalue_reference<T>::type val();
template<class T> struct id{typedef T type;};
template<class T, class... P> struct sum_type;
template<class T> struct sum_type<T> : id<T> {};
template<class T, class U, class... P> struct sum_type<T,U,P...>
: sum_type< decltype( val<const T&>() + val<const U&>() ), P... > {};
This way, we can replace decltype in your program with typename sum_type<T,P...>::type and it will compile.
Edit: Since this actually returns decltype((a+b)+c) instead of decltype(a+(b+c)) which would be closer to how you use addition, you could replace the last specialization with this:
template<class T, class U, class... P> struct sum_type<T,U,P...>
: id<decltype(
val<T>()
+ val<typename sum_type<U,P...>::type>()
)>{};
Apparently you can't use decltype in a recursive manner (at least for the moment, maybe they'll fix it)
You can use a template structure to determine the type of the sum
It looks ugly but it works
#include <iostream>
using namespace std;
template<typename... T>
struct TypeOfSum;
template<typename T>
struct TypeOfSum<T> {
typedef T type;
};
template<typename T, typename... P>
struct TypeOfSum<T,P...> {
typedef decltype(T() + typename TypeOfSum<P...>::type()) type;
};
template <class T>
T sum(const T& in)
{
return in;
}
template <class T, class... P>
typename TypeOfSum<T,P...>::type sum(const T& t, const P&... p)
{
return t + sum(p...);
}
int main()
{
cout << sum(5, 10.0, 22.2) << endl;
}
C++14's solution:
template <class T, class... P>
decltype(auto) sum(const T& t, const P&... p){
return t + sum(p...);
}
Return type is deducted automatically.
See it in online compiler
Or even better if you want to support different types of references:
template <class T, class... P>
decltype(auto) sum(T &&t, P &&...p)
{
return std::forward<T>(t) + sum(std::forward<P>(p)...);
}
See it in online compiler
If you need a natural order of summation (that is (((a+b)+c)+d) instead of (a+(b+(c+d)))), then the solution is more complex:
template <class A>
decltype(auto) sum(A &&a)
{
return std::forward<A>(a);
}
template <class A, class B>
decltype(auto) sum(A &&a, B &&b)
{
return std::forward<A>(a) + std::forward<B>(b);
}
template <class A, class B, class... C>
decltype(auto) sum(A &&a, B &&b, C &&...c)
{
return sum( sum(std::forward<A>(a), std::forward<B>(b)), std::forward<C>(c)... );
}
See it in online compiler
Another answer to the last question with less typing by using C++11's std::common_type: Simply use
std::common_type<T, P ...>::type
as return type of your variadic sum.
Regarding std::common_type, here is an excerpt from http://en.cppreference.com/w/cpp/types/common_type:
For arithmetic types, the common type may also be viewed as the type
of the (possibly mixed-mode) arithmetic expression such as T0() + T1()
+ ... + Tn().
But obviously this works only for arithmetic expressions and doesn't cure the general problem.
I provide this improvement to the accepted answer. Just two structs
#include <utility>
template <typename P, typename... Ps>
struct sum_type {
using type = decltype(std::declval<P>() + std::declval<typename sum_type<Ps...>::type>());
};
template <typename P>
struct sum_type<P> {
using type = P;
};
Now just declare your functions as
template <class T>
auto sum(const T& in) -> T
{
return in;
}
template <class P, class ...Ps>
auto sum(const P& t, const Ps&... ps) -> typename sum_type<P, Ps...>::type
{
return t + sum(ps...);
}
With this, your test code now works
std::cout << sum(5, 10.0, 22.2, 33, 21.3, 55) << std::endl;
146.5
Right way to do:
#include <utility>
template <typename... Args>
struct sum_type;
template <typename... Args>
using sum_type_t = typename sum_type<Args...>::type;
template <typename A>
struct sum_type<A> {
using type = decltype( std::declval<A>() );
};
template <typename A, typename B>
struct sum_type<A, B> {
using type = decltype( std::declval<A>() + std::declval<B>() );
};
template <typename A, typename B, typename... Args>
struct sum_type<A, B, Args...> {
using type = sum_type_t< sum_type_t<A, B>, Args... >;
};
template <typename A>
sum_type_t<A> sum(A &&a)
{
return (std::forward<A>(a));
}
template <typename A, typename B>
sum_type_t<A, B> sum(A &&a, B &&b)
{
return (std::forward<A>(a) + std::forward<B>(b));
}
template <typename A, typename B, typename... C>
sum_type_t<A, B, C...> sum(A &&a, B &&b, C &&...args)
{
return sum( sum(std::forward<A>(a), std::forward<B>(b)), std::forward<C>(args)... );
}
https://coliru.stacked-crooked.com/a/a5a0e8019e40b8ba
This completely preserves resulting type of operations (even r-value referenceness). The order of operations is natural: (((a+b)+c)+d).
For C++17:
template <class... P>
auto sum(const P... p){
return (p + ...);
}
int main()
{
std::cout << sum(1, 3.5, 5) << std::endl;
return EXIT_SUCCESS;
}
Read about folding expressions.
I'd like to generically "pickle" function calls so they can be executed later. The return type of those functions will always be void (for now). Something like this:
template<typename F, typename... Args>
std::function<void()>
pickle(F function, Args&&... args) {
return std::bind(F, args...);
}
The problem is, if args contains a const reference, std::bind tries to copy construct the value, which is not always desired or even valid if the type lacks a copy constructor. How do I forward the arguments in a way that uses std::ref for lvalue references and the normal std::forward for lvalue references?
Example
#include <functional>
class NonCopyable {
public:
NonCopyable() {}
NonCopyable(const NonCopyable&) = delete;
};
template<typename F, typename... Args>
std::function<void()>
pickle(F function, Args&&... args)
{
return std::bind(function, std::forward<Args>(args)...);
}
int main()
{
NonCopyable obj;
auto f = pickle(
[](const NonCopyable&) {},
obj
);
return 0;
}
The above snippet won't compile, complaining about the deleted copy constructor. (I used forward here because someone suggested it, but has since deleted their answer, it seems).
Overloading, yay.
// also does the correct thing for `T const`
template<class T>
std::reference_wrapper<T> maybe_ref(T& v, int){ return std::ref(v); }
// just forward rvalues along
template<class T>
T&& maybe_ref(T&& v, long){ return std::forward<T>(v); }
template<typename F, typename... Args>
std::function<void()>
pickle(F function, Args&&... args) {
return std::bind(function, maybe_ref(std::forward<Args>(args), 0)...);
}
The int/long parameters and 0 argument disambiguate the lvalue case for compilers that find the overloads to be ambiguous, and doesn't do any harm otherwise.
This is a bit ugly (overuse of enable_if), but it works:
template<typename T> typename std::enable_if<
!std::is_lvalue_reference<T>::value, T &&>::type
forward_as_ref(typename std::remove_reference<T>::type &t) {
return static_cast<T &&>(t);
}
template<typename T> typename std::enable_if<
!std::is_lvalue_reference<T>::value, T &&>::type
forward_as_ref(typename std::remove_reference<T>::type &&t) {
return t;
}
template<typename T> typename std::enable_if<
std::is_lvalue_reference<T>::value,
std::reference_wrapper<typename std::remove_reference<T>::type>>::type
forward_as_ref(T t) {
return t;
}
Here's a version using class template specialization instead:
template<typename T> struct forward_as_ref_type {
typedef T &&type;
};
template<typename T> struct forward_as_ref_type<T &> {
typedef std::reference_wrapper<T> type;
};
template<typename T> typename forward_as_ref_type<T>::type forward_as_ref(
typename std::remove_reference<T>::type &t) {
return static_cast<typename forward_as_ref_type<T>::type>(t);
}
template<typename T> T &&forward_as_ref(
typename std::remove_reference<T>::type &&t) {
return t;
}
I want to write a simple adder (for giggles) that adds up every argument and returns a sum with appropriate type.
Currently, I've got this:
#include <iostream>
using namespace std;
template <class T>
T sum(const T& in)
{
return in;
}
template <class T, class... P>
auto sum(const T& t, const P&... p) -> decltype(t + sum(p...))
{
return t + sum(p...);
}
int main()
{
cout << sum(5, 10.0, 22.2) << endl;
}
On GCC 4.5.1 this seems to work just fine for 2 arguments e.g. sum(2, 5.5) returns with 7.5. However, with more arguments than this, I get errors that sum() is simply not defined yet. If I declare sum() like this however:
template <class T, class P...>
T sum(const T& t, const P&... p);
Then it works for any number of arguments, but sum(2, 5.5) would return integer 7, which is not what I would expect.
With more than two arguments I assume that decltype() would have to do some sort of recursion to be able to deduce the type of t + sum(p...). Is this legal C++0x? or does decltype() only work with non-variadic declarations? If that is the case, how would you write such a function?
I think the problem is that the variadic function template is only considered declared after you specified its return type so that sum in decltype can never refer to the variadic function template itself. But I'm not sure whether this is a GCC bug or C++0x simply doesn't allow this. My guess is that C++0x doesn't allow a "recursive" call in the ->decltype(expr) part.
As a workaround we can avoid this "recursive" call in ->decltype(expr) with a custom traits class:
#include <iostream>
#include <type_traits>
using namespace std;
template<class T> typename std::add_rvalue_reference<T>::type val();
template<class T> struct id{typedef T type;};
template<class T, class... P> struct sum_type;
template<class T> struct sum_type<T> : id<T> {};
template<class T, class U, class... P> struct sum_type<T,U,P...>
: sum_type< decltype( val<const T&>() + val<const U&>() ), P... > {};
This way, we can replace decltype in your program with typename sum_type<T,P...>::type and it will compile.
Edit: Since this actually returns decltype((a+b)+c) instead of decltype(a+(b+c)) which would be closer to how you use addition, you could replace the last specialization with this:
template<class T, class U, class... P> struct sum_type<T,U,P...>
: id<decltype(
val<T>()
+ val<typename sum_type<U,P...>::type>()
)>{};
Apparently you can't use decltype in a recursive manner (at least for the moment, maybe they'll fix it)
You can use a template structure to determine the type of the sum
It looks ugly but it works
#include <iostream>
using namespace std;
template<typename... T>
struct TypeOfSum;
template<typename T>
struct TypeOfSum<T> {
typedef T type;
};
template<typename T, typename... P>
struct TypeOfSum<T,P...> {
typedef decltype(T() + typename TypeOfSum<P...>::type()) type;
};
template <class T>
T sum(const T& in)
{
return in;
}
template <class T, class... P>
typename TypeOfSum<T,P...>::type sum(const T& t, const P&... p)
{
return t + sum(p...);
}
int main()
{
cout << sum(5, 10.0, 22.2) << endl;
}
C++14's solution:
template <class T, class... P>
decltype(auto) sum(const T& t, const P&... p){
return t + sum(p...);
}
Return type is deducted automatically.
See it in online compiler
Or even better if you want to support different types of references:
template <class T, class... P>
decltype(auto) sum(T &&t, P &&...p)
{
return std::forward<T>(t) + sum(std::forward<P>(p)...);
}
See it in online compiler
If you need a natural order of summation (that is (((a+b)+c)+d) instead of (a+(b+(c+d)))), then the solution is more complex:
template <class A>
decltype(auto) sum(A &&a)
{
return std::forward<A>(a);
}
template <class A, class B>
decltype(auto) sum(A &&a, B &&b)
{
return std::forward<A>(a) + std::forward<B>(b);
}
template <class A, class B, class... C>
decltype(auto) sum(A &&a, B &&b, C &&...c)
{
return sum( sum(std::forward<A>(a), std::forward<B>(b)), std::forward<C>(c)... );
}
See it in online compiler
Another answer to the last question with less typing by using C++11's std::common_type: Simply use
std::common_type<T, P ...>::type
as return type of your variadic sum.
Regarding std::common_type, here is an excerpt from http://en.cppreference.com/w/cpp/types/common_type:
For arithmetic types, the common type may also be viewed as the type
of the (possibly mixed-mode) arithmetic expression such as T0() + T1()
+ ... + Tn().
But obviously this works only for arithmetic expressions and doesn't cure the general problem.
I provide this improvement to the accepted answer. Just two structs
#include <utility>
template <typename P, typename... Ps>
struct sum_type {
using type = decltype(std::declval<P>() + std::declval<typename sum_type<Ps...>::type>());
};
template <typename P>
struct sum_type<P> {
using type = P;
};
Now just declare your functions as
template <class T>
auto sum(const T& in) -> T
{
return in;
}
template <class P, class ...Ps>
auto sum(const P& t, const Ps&... ps) -> typename sum_type<P, Ps...>::type
{
return t + sum(ps...);
}
With this, your test code now works
std::cout << sum(5, 10.0, 22.2, 33, 21.3, 55) << std::endl;
146.5
Right way to do:
#include <utility>
template <typename... Args>
struct sum_type;
template <typename... Args>
using sum_type_t = typename sum_type<Args...>::type;
template <typename A>
struct sum_type<A> {
using type = decltype( std::declval<A>() );
};
template <typename A, typename B>
struct sum_type<A, B> {
using type = decltype( std::declval<A>() + std::declval<B>() );
};
template <typename A, typename B, typename... Args>
struct sum_type<A, B, Args...> {
using type = sum_type_t< sum_type_t<A, B>, Args... >;
};
template <typename A>
sum_type_t<A> sum(A &&a)
{
return (std::forward<A>(a));
}
template <typename A, typename B>
sum_type_t<A, B> sum(A &&a, B &&b)
{
return (std::forward<A>(a) + std::forward<B>(b));
}
template <typename A, typename B, typename... C>
sum_type_t<A, B, C...> sum(A &&a, B &&b, C &&...args)
{
return sum( sum(std::forward<A>(a), std::forward<B>(b)), std::forward<C>(args)... );
}
https://coliru.stacked-crooked.com/a/a5a0e8019e40b8ba
This completely preserves resulting type of operations (even r-value referenceness). The order of operations is natural: (((a+b)+c)+d).
For C++17:
template <class... P>
auto sum(const P... p){
return (p + ...);
}
int main()
{
std::cout << sum(1, 3.5, 5) << std::endl;
return EXIT_SUCCESS;
}
Read about folding expressions.