Specifying default parameter when calling C++ function - c++

Suppose I have code like this:
void f(int a = 0, int b = 0, int c = 0)
{
//...Some Code...
}
As you can evidently see above with my code, the parameters a,b, and c have default parameter values of 0. Now take a look at my main function below:
int main()
{
//Here are 4 ways of calling the above function:
int a = 2;
int b = 3;
int c = -1;
f(a, b, c);
f(a, b);
f(a);
f();
//note the above parameters could be changed for the other variables
//as well.
}
Now I know that I can't just skip a parameter, and let it have the default value, because that value would evaluate as the parameter at that position. What I mean is, that I cannot, say call, f(a,c), because, c would be evaluated as b, which is what I don't want, especially if c is the wrong type. Is there a way for the calling function to specify in C++, to use whatever default parameter value there is for the function in any given position, without being limited to going backwards from the last parameter to none? Is there any reserved keyword to achieve this, or at least a work-around? An example I can give would be like:
f(a, def, c) //Where def would mean default.

There isn't a reserved word for this, and f(a,,c) is not valid either. You can omit a number of rightmost optional parameters, as you show, but not the middle one like that.
http://www.learncpp.com/cpp-tutorial/77-default-parameters/
Quoting directly from the link above:
Multiple default parameters
A function can have multiple default parameters:
void printValues(int x=10, int y=20, int z=30)
{
std::cout << "Values: " << x << " " << y << " " << z << '\n';
}
Given the following function calls:
printValues(1, 2, 3);
printValues(1, 2);
printValues(1);
printValues();
The following output is produced:
Values: 1 2 3
Values: 1 2 30
Values: 1 20 30
Values: 10 20 30
Note that it is impossible to supply a user-defined value for z
without also supplying a value for x and y. This is because C++ does
not support a function call syntax such as printValues(,,3). This has
two major consequences:
1) All default parameters must be the rightmost parameters. The
following is not allowed:
void printValue(int x=10, int y); // not allowed
2) If more than one default parameter exists, the leftmost default
parameter should be the one most likely to be explicitly set by the
user.

As workaround, you may (ab)use boost::optional (until std::optional from c++17):
void f(boost::optional<int> oa = boost::none,
boost::optional<int> ob = boost::none,
boost::optional<int> oc = boost::none)
{
int a = oa.value_or(0); // Real default value go here
int b = ob.value_or(0); // Real default value go here
int c = oc.value_or(0); // Real default value go here
//...Some Code...
}
and then call it
f(a, boost::none, c);

Not exactly what you asked for, but you can use std::bind() to fix a value for a parameter.
Something like
#include <functional>
void f(int a = 0, int b = 0, int c = 0)
{
//...Some Code...
}
int main()
{
// Here are 4 ways of calling the above function:
int a = 2;
int b = 3;
int c = -1;
f(a, b, c);
f(a, b);
f(a);
f();
// note the above parameters could be changed
// for the other variables as well.
using namespace std::placeholders; // for _1, _2
auto f1 = std::bind(f, _1, 0, _2);
f1(a, c); // call f(a, 0, c);
return 0;
}
With std::bind() you can fix values different from default parameters' values or values for parameters without default values.
Take into account that std::bind() is available only from C++11.

You already have an accepted answer, but here's another workaround (that - I believe - has advantages over the other proposed workarounds):
You can strong-type the arguments:
struct A { int value = 0; };
struct B { int value = 2; };
struct C { int value = 4; };
void f(A a = {}, B b = {}, C c = {}) {}
void f(A a, C c) {}
int main()
{
auto a = 0;
auto b = -5;
auto c = 1;
f(a, b, c);
f(a, C{2});
f({}, {}, 3);
}
Advantages:
it's simple and easy to maintain (one line per argument).
provides a natural point for constricting the API further (for example, "throw if B's value is negative").
it doesn't get in the way (works with default construction, works with intellisense/auto-complete/whatever as good as any other class)
it is self-documenting.
it's as fast as the native version.
Disadvantages:
increases name pollution (better put all this in a namespace).
while simple, it is still more code to maintain (than just defining the function directly).
it may raise a few eyebrows (consider adding a comment on why strong-typing is needed)

If all parameters of the function were of distinct types, you could find out which parameters were passed and which were not and choose the default value for the latter.
In order to achieve the distinct type requirement, you can wrap your parameters and pass it to a variadic function template.
Then even the order of the argument does not matter anymore:
#include <tuple>
#include <iostream>
#include <type_traits>
// -----
// from http://stackoverflow.com/a/25958302/678093
template <typename T, typename Tuple>
struct has_type;
template <typename T>
struct has_type<T, std::tuple<>> : std::false_type {};
template <typename T, typename U, typename... Ts>
struct has_type<T, std::tuple<U, Ts...>> : has_type<T, std::tuple<Ts...>> {};
template <typename T, typename... Ts>
struct has_type<T, std::tuple<T, Ts...>> : std::true_type {};
template <typename T, typename Tuple>
using tuple_contains_type = typename has_type<T, Tuple>::type;
//------
template <typename Tag, typename T, T def>
struct Value{
Value() : v(def){}
Value(T v) : v(v){}
T v;
};
using A = Value<struct A_, int, 1>;
using B = Value<struct B_, int, 2>;
using C = Value<struct C_, int, 3>;
template <typename T, typename Tuple>
std::enable_if_t<tuple_contains_type<T, Tuple>::value, T> getValueOrDefaultImpl(Tuple t)
{
return std::get<T>(t);
}
template <typename T, typename Tuple>
std::enable_if_t<!tuple_contains_type<T, Tuple>::value, T> getValueOrDefaultImpl(Tuple)
{
return T{};
}
template <typename InputTuple, typename... Params>
auto getValueOrDefault(std::tuple<Params...>, InputTuple t)
{
return std::make_tuple(getValueOrDefaultImpl<Params>(t)...);
}
template <typename... Params, typename ArgTuple>
auto getParams(ArgTuple argTuple)
{
using ParamTuple = std::tuple<Params...>;
ParamTuple allValues = getValueOrDefault(ParamTuple{}, argTuple);
return allValues;
}
template <typename... Args>
void f(Args ... args)
{
auto allParams = getParams<A,B,C>(std::make_tuple(args...));
std::cout << "a = " << std::get<A>(allParams).v << " b = " << std::get<B>(allParams).v << " c = " << std::get<C>(allParams).v << std::endl;
}
int main()
{
A a{10};
B b{100};
C c{1000};
f(a, b, c);
f(b, c, a);
f(a, b);
f(a);
f();
}
output
a = 10 b = 100 c = 1000
a = 10 b = 100 c = 1000
a = 10 b = 100 c = 3
a = 10 b = 2 c = 3
a = 1 b = 2 c = 3
live example

I will just use static functions to define default values that can change:
class defValsExample
{
public:
defValsExample() {
}
static int f1def_a() { return 1; }
static int f1def_b() { return 2; }
int f1(int a = f1def_a(), int b = f1def_b()) {
return a+b;
}
};
int main()
{
defValsExample t;
int c = t.f1(t.f1def_a(),4);
}

Related

Template function with different amount and type of parameters [duplicate]

This question already has answers here:
Variadic template pack expansion
(6 answers)
Closed 2 years ago.
so I have a template function:
int someFunc(int num) {
int a = num / 2;
int b = num + 10;
return a + num / b + b;
}
template<typename Type, typename Function>
Type test() {
Function func = (Function)&someFunc;
return func();
}
In the function test I now want to call other different functions, not only someFunc, which have different parameters. Like this:
template<typename Type, typename Function>
Type test(args) {
Function func = (Function)&someFunc; //This is later replaced with an variable, that contains a address to a different function
return func(args);
}
args should be like a list of arguments, all of which can be different types. This list I want to pass to the func. That’s what it would look like:
typedef int(__cdecl* tSomeFunc)(int, int, BYTE*);
int i = test<int, tSomeFunc>(4, 6, "Hey");
template<typename Type, typename Function>
Type test(argument list that accepts all the arguments given above) {
Function func = (Function)&someFunc;
return func(argument list);
}
Is that possible? If not, is there any other way?
You can do this via Parameter pack. It folds the Arguments that you have given.
I wrote a small example. I hope it helps you.
template<typename Func, typename... Args>
auto test(Func f, Args&... args)
{
return f(args...);
}
template<typename Func, typename... Args>
auto test(Func f, Args&&... args)
{
return f(std::forward<Args>(args)...);
}
double func(double a, float b)
{
return a * b;
}
int foo(int a, int b, int c)
{
return a + b + c +5;
}
int main()
{
std::cout << test(foo, 5, 10 , 20) << "\n";
std::cout << test(func, 20.5, 100.4) << "\n";
double x = 100.31;
double y = 23.52;
std::cout << test(func, x, y);
}
The function uses trailing return type. test function has two overloads. One for the temporary object that uses perfect forwarding, std::forward, to avoid unnecessary copy costs. Other ones for lvalue objects.

Type deduction time

I ran into this problem earlier today. In the following code:
template <int> struct Holder {};
template <typename> struct Helper { using T = Holder<__COUNTER__>; }; // ???
int main() {
auto a = typename Helper<bool>::T();
auto b = typename Helper<int>::T();
std::cout << (typeid(a) == typeid(b)) << std::endl;
return 0;
}
When compiled and executed with:
g++ test.cpp -std=c++11 -o test
./test
It prints out 1 instead of 0, meaning that the 2 Ts in Helper<int> and Helper<bool> are the same type, which makes me wonder:
Why the line marked with // ??? is executed only once instead of once for each of the type?
Is there a way to force the line to be executed once for each of the type and preferably without modifying the definition of Holder?
====================================================
Clarifications:
The (closer to) real scenario is:
The struct Holder is defined in a header from a third-party library. The type for the struct is actually very complicated and the library writer provides users with another macro:
template <bool, int> struct Holder {};
#define DEF_HOLDER(b) Holder<b, __COUNTER__>()
At some point of the program, I want to take a "snapshot" of the type with current counter by aliasing the type so that it could be used in a function:
template <bool b>
struct Helper { using T = decltype(DEF_HOLDER(b)); };
template <bool b, typename R = typename Helper<b>::T>
R Func() {
return R();
}
// Note that the following does not work:
// Since the 2 types generated by DEF_HOLDER do not match.
template <bool b>
auto Func() -> decltype(DEF_HOLDER(b)) {
return DEF_HOLDER(b);
}
The problem here is that the following 2 usage has inconsistent semantics as illustrated:
int main() {
auto a = DEF_HOLDER(true);
auto b = DEF_HOLDER(true);
auto c = Func<true>();
auto d = Func<true>();
std::cout << (typeid(a) == typeid(b)) << std::endl; // prints 0
std::cout << (typeid(c) == typeid(d)) << std::endl; // prints 1
return 0;
}
In my use case, it is important for multiple invocation of Func to return different types as it does with invoking DEF_HOLDER directly.
The symbol __COUNTER__ is a preprocessor macro, it's expanded once only.
That means T will always be Holder<0> (since __COUNTER__ starts at zero), no matter the type used for the template Helper.
See e.g. this GCC predefined macro reference for more information about __COUNTER__.
I am not sure whether I completely understand the problem, but since C++14 there is no need to use DEF_HOLDER two times. The following code also works:
template <bool b>
auto Func() {
return DEF_HOLDER(b);
}
If you want a different type for every function call, you can add the int parameter:
template <bool b, int i>
auto Func()
{
return Holder<b, i>();
}
You could hide this int in a macro:
#define FUNC(b) Func<b,__COUNTER__>();
Then a,b and c,d have the same semantics:
int main() {
auto a = DEF_HOLDER(true);
auto b = DEF_HOLDER(true);
auto c = FUNC(true);
auto d = FUNC(true);
std::cout << (typeid(a) == typeid(b)) << std::endl; // prints 0
std::cout << (typeid(c) == typeid(d)) << std::endl; // prints 0
return 0;
}

Instantiation of function template with variadic parameter pack

Suppose say I have this code:
template<int... Us>
struct Matrix{};
template<int... U1, int... U2>
auto compute(Matrix<U1...>, Matrix<U2...>){return 0;}
Matrix<1> a; Matrix<2,3> b;
Matrix<1,2> c; Matrix<3> d;
int main(){
compute(a,b);
compute(c,d);
auto fp = &compute<1,2,3>;
fp(a,b);
fp(c,d);
}
Would the two compute() calls instantiate just one function template i.e. compute<1,2,3> or would there be two different instantiations depending on the arguments?
I wanted to confirm this by taking a function pointer to the particular instantiation and see if I could call the function with the 2 different sets of arguments using the same function pointer but I get the following errors at the line where I call fp(a,b):
[x86-64 gcc 8.2 #1] error: could not convert 'a' from
'Matrix<#'nontype_argument_pack' not supported by
dump_expr#<expression error>>' to 'Matrix<#'nontype_argument_pack' not
supported by dump_expr#<expression error>>'
Parameter packs are greedy.
&compute<1,2,3> is, in pseudo-code, &compute< U1={1,2,3}, U2={} >.
Getting a pointer to the individual computes is annoying.
template<class U1s, class U2s>
struct get_computer;
template<int...U1, int...U2>
struct get_computer<std::integer_sequence<int, U1...>, std::integer_sequence<int, U2...>> {
using compute_type = int(*)(Matrix<U1...>, Matrix<U2...>);
compute_type operator()() const { return compute; }
};
we can then do
auto fp1 = get_computer<std::integer_sequence<int, 1>, std::integer_sequence<int, 2, 3>>{}();
auto fp2 = get_computer<std::integer_sequence<int, 1, 2>, std::integer_sequence<int, 3>>{}();
and fp1 and fp2 are different types.
Would the two compute() calls instantiate just one function template i.e. compute<1,2,3> or would there be two different instantiations depending on the arguments?
Very differently. The function that is invoked when you write compute(a, b) is a function that takes a Matrix<1> and a Matrix<2,3>. The function that is invoked when you write compute(c, d) is a function that takes a Matrix<1,2> and a Matrix<3>.
But when you write this:
auto fp = &compute<1,2,3>;
There is no way to say which of U1... or U2... those values refer to. What every compiler does is slurp all of the arguments into the first pack - so fp ends up being a int(*)(Matrix<1,2,3>, Matrix<>). In other words, this version is a function that takes a Matrix<1,2,3> and a Matrix<>. Which is different from both of the original calls.
And indeed, since the two original calls were calls to two different functions, it would not be possible to take a single function pointer to both of them. What you could do instead is construct a function object that does the right thing:
auto fp = [](auto m1, auto m2){ return compute(m1, m2); };
This works (try it), but is a very different kind of thing.
Different approach. No need integer sequence or temporary functor object. Link: https://gcc.godbolt.org/z/W4V6gf
template<int... V1>
struct Mat1 {
template<int... V2>
struct Mat2 {
using compute_type = int(*)(Matrix<V1...>, Matrix<V2...>);
};
};
void foo() {
{
using compute_type = Mat1<1>::Mat2<2,3>::compute_type;
compute_type ct = compute;
ct(a, b);
//ct(c, d); //This wont compile
}
{
using compute_type = Mat1<1,2>::Mat2<3>::compute_type;
compute_type ct = compute;
ct(c, d);
//ct(a, b); //This wont compile
}
}
Even More Generic
template<int... Us>
struct Matrix{};
Matrix<1> a; Matrix<2,3> b;
Matrix<1,2> c; Matrix<3> d;
template<int... U1, int... U2>
auto compute(Matrix<U1...>, Matrix<U2...>){return 0;}
template<class Scalar, template<Scalar...> class MatType>
struct VArg {
template<Scalar... V1>
struct arg1 {
using type = MatType<V1...>;
template<Scalar... V2>
struct arg2 {
using type1 = type;
using type2 = MatType<V2...>;
};
};
};
template<class args_gen>
struct compute_type {
using type = int(*)(typename args_gen::type1, typename args_gen::type2);
};
void foo() {
using int_matrix_gen = VArg<int, Matrix>;
{
using args_ab = int_matrix_gen::arg1<1>::arg2<2,3>;
compute_type<args_ab>::type cptr = compute;
cptr(a, b);
//cptr(c, d); This wont compile
}
{
using args_cd = int_matrix_gen::arg1<1,2>::arg2<3>;
compute_type<args_cd>::type cptr = compute;
cptr(c, d);
//cptr(a, b); This wont compile
}
}
Even more Generic'er: https://gcc.godbolt.org/z/EF6OK9 supporting functions with 3 Matrix arguments.

Function with variadic arguments of a custom struct type

I come from a Swift background and, though I know some C as well, this is my first time writing C++ code.
In Swift it is possible to write a function that takes any number of arguments:
func foo(bar: String...) {
// ...
}
and bar can be of any type (String, Bool, Struct, Enum, etc).
I was wondering if the same can be done in C++. So, ideally I would write:
struct X {
string s;
X(int);
// ...
}
void foo(string s, ...) {
// ...
}
foo("mystr", X(1), X(2), X(3));
and inside foo I would somehow be able to access the list of arguments, somewhat akin to a printf function.
Right now I'm using a vector<X> as argument, since all the arguments have type X. However, that makes calling foo somewhat ugly, in my opinion:
foo("mystr", { X(1), X(2), X(3) });
Any solution I'm not seeing due to my strong lack of knowledge towards C++?
Edit:
This is what I want done specifically inside foo:
string ssub(string s, vector<X> v) {
int index, i = 0;
while (1) {
index = (int)s.find(SUB);
if (index == string::npos) { break; }
s.erase(index, string(SUB).size());
s.insert(index, v[i].tostr());
i++;
}
return s;
}
Basically, as long as I'm given a way to sequentially access the arguments, all is good.
Here's one of many ways.
You can copy/paste this entire program into your IDE/editor.
#include <utility>
#include <iostream>
#include <typeinfo>
#include <string>
//
// define a template function which applies the unary function object func
// to each element in the parameter pack elems.
// #pre func(std::forward<Elements>(elems)) must be well formed for each elems
// #returns void
//
template<class Function, class...Elements>
auto do_for_all(Function&& func, Elements&&...elems)
{
using expand = int[];
void(expand { 0, (func(elems), 0)... });
}
// a test structure which auto-initialises all members
struct X
{
int i = 0;
std::string s = "hello";
double d = 4.4;
};
//
// The function foo
// introduces itself by writing intro to the console
// then performs the function object action on each of args
// #note all arguments are perfectly forwarded - no arguments are copied
//
template<class...Args>
auto foo(const std::string& intro, Args&&...args)
{
std::cout << "introducing : " << intro << std::endl;
auto action = [](auto&& arg)
{
std::cout << "performing action on: " << arg
<< " which is of type " << typeid(arg).name() << std::endl;
};
do_for_all(action, std::forward<Args>(args)...);
}
int main()
{
// make an X
auto x = X(); // make an X
// foo it with the intro "my X"
foo("my X", x.i, x.s, x.d);
}
example output:
introducing : my X
performing action on: 0 which is of type i
performing action on: hello which is of type NSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE
performing action on: 4.4 which is of type d
You can use variadic templates (since C++11):
template <typename ... Type>
void foo(Type& ... args) {
// do whatever you want, but this may be tricky
}
foo(X(1), X(2), X(3));
Example of variadic templates: min function
This is the code I wrote to get rid of ugly calls to std::min when calculating minimum of many values.
#include <type_traits>
namespace my {
template <typename A, typename B>
auto min(const A& a, const B& b) -> typename std::common_type<A, B>::type {
return (a<b)?a:b;
}
template <typename A, typename B, typename ... T >
auto min(const A& a, const B& b, const T& ... c) -> typename std::common_type<A, B, T ...>::type {
const typename std::common_type<A, B, T ...>::type tmp = my::min(b, c ...);
return (a<tmp)?a:tmp;
}
}
// calculating minimum with my::min
my::min(3, 2, 3, 5, 23, 98);
// doing the same with std::min
std::min(3, std::min(2, std::min(3, std::min(5, std::min(23, 98))))); // ugh, this is ugly!
Here's the tricky part: you can't cycle through the parameter pack like you do with vectors. You'll have to do some recursion as shown in the example.
You could write a variadic template function, pass the arguments into some std::initializer_list and iterate over the list, for example:
#include <initializer_list>
template <typename ... Args>
void foo(Args && ... args) {
std::initializer_list<X> as{std::forward<Args>(args)...};
for (auto const & x : as)
// Use x here
}
int main() {
foo(1, 2, 3, 4, 5);
}
Note also, that you might want to change the argument list and type of the initializer list to meet your exact use-case. E.g. use Args * ... args and std::initializer_list<X *> or similar.

c++ Default paramaters: is it possible to override a default parameter without overriding earlier default parameters

I have a function:
int function(int a, int b = 1, int c = 2){
return a+b+c;
}
I want to set the value of the "c" variable to 3, but don't want to set the value of "b"
In a language like python I can do this:
function(23,c=3)
However in c++ I cant find a way to do something like that. All examples I could find involved setting the value of "b" before the value of "c", like this:
function(23,1,3);
How can I set the value of a default parameter directly?
This is not possible in C++ (at least not directly). You have the provide all parameters up to the last one you want to provide, and in the order given by the declaration.
You can not do that in C++.
As a workaround you could wrap all parameters as fields with default value in a class (or a struct). You can then have multiple constructors for that class that allow you to set only those fields you are really interested in changing with respect to default.
It is possible in c++... if you're willing to jump through some hoops.
For fun, here is an example of how it might be done:
#include <iostream>
#include <tuple>
#include <type_traits>
#include <utility>
//
// utility to check whether a type is in a list of types
//
template<class T, class...Ts> struct is_in;
template<class T, class U>
struct is_in<T, U>
: std::is_same<T, U>::type {};
template<class T, class U, class...Rest>
struct is_in<T, U, Rest...>
: std::integral_constant<bool, std::is_same<T, U>::value || is_in<T, Rest...>::value>
{};
//
// a wrapper around fundamental types so we can 'name' types
//
template<class Type, class Tag>
struct fundamental {
using value_type = Type;
using tag_type = Tag;
fundamental(Type x) : _x(x) {}
operator const Type&() const { return _x; }
operator Type&() { return _x; }
Type _x;
};
//
// a utility to figure out a fundamental type's value or to take it's default value if it's not present
//
template<class Fundamental, class Tuple, typename = void>
struct value_of_impl
{
static typename Fundamental::value_type apply(const Tuple& t)
{
return Fundamental::tag_type::dflt;
}
};
template<class Fundamental, class...Types>
struct value_of_impl<Fundamental, std::tuple<Types...>, std::enable_if_t<is_in<Fundamental, Types...>::value>>
{
static typename Fundamental::value_type apply(const std::tuple<Types...>& t)
{
return typename Fundamental::value_type(std::get<Fundamental>(t));
}
};
template<class Fundamental, class Tuple>
decltype(auto) value_of(const Tuple& t)
{
return value_of_impl<Fundamental, Tuple>::apply(t);
}
//
// some tag names to differentiate parameter 'name' types
//
struct a_tag { static constexpr int dflt = 0; };
struct b_tag { static constexpr int dflt = 1; };
struct c_tag { static constexpr int dflt = 2; };
//
// define some parameter 'names'
//
using a = fundamental<int, a_tag>;
using b = fundamental<int, b_tag>;
using c = fundamental<int, c_tag>;
//
// the canonical implementation of the function
//
void func(int a, int b, int c)
{
std::cout << a << ", " << b << ", " << c << std::endl;
}
//
// a version that forwards the values of fundamental types in a tuple, or their default values if not present
//
template<class...Fundamentals>
void func(std::tuple<Fundamentals...> t)
{
func(value_of<a>(t),
value_of<b>(t),
value_of<c>(t));
}
//
// a version that converts a variadic argument list of fundamentals into a tuple (that we can search)
//
template<class...Fundamentals>
void func(Fundamentals&&...fs)
{
return func(std::make_tuple(fs...));
}
//
// a test
//
using namespace std;
auto main() -> int
{
func();
func(a(5));
func(c(10), a(5));
func(b(20), c(10), a(5));
return 0;
}
expected output:
0, 1, 2
5, 1, 2
5, 1, 10
5, 20, 10
You can't do that directly, but you can use Named Parameter Idiom (although criticized).
The idea is to create an object encapsulating all parameters, initialize it using method chaining and finally call the function, so the code would look like:
int v = function(params(23).c(3));
Something like this could be done with the named parameter idiom. Here's how it might look in use to have optional parameters (sans the default parameter values):
/*
int function(int a, int b = 1, int c = 2){
return a+b+c;
}
*/
int function( Parameters &p ) {
/* ... */
}
void someOtherFunction() {
function( Parameters().parmW(/*...*/)
/* parmX was omitted here */
.parmY(/*...*/)
.parmZ(/*...*/)
);
Adding default parameters could be done in a few ways. function could be replaced with a class whose purpose is to perform those actions. Parameters could also be written to know which flags were set, then function passes in default values before it begins executing. I'm sure there's plenty of ways to do this, perhaps some a lot better than what I've suggested.