mirror of
https://github.com/ETLCPP/etl.git
synced 2026-10-28 18:07:10 +08:00
Added etl::function variants that take both the instance and member function at compile time.
This commit is contained in:
parent
49fe0054d2
commit
53412f898f
@ -0,0 +1,149 @@
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
#include "function.h"
|
||||
|
||||
//********************************
|
||||
// Fake UART Rx register.
|
||||
//********************************
|
||||
char get_char()
|
||||
{
|
||||
static char c = 'A';
|
||||
return c++;
|
||||
}
|
||||
|
||||
//********************************
|
||||
// Interrupt vectors & callbacks.
|
||||
//********************************
|
||||
// Callback interfaces.
|
||||
// Note that they do not require any knowledge about the callee apart from the parameter type.
|
||||
etl::ifunction<void>* timer1_callback; // A pointer to a callback taking no parameters.
|
||||
etl::ifunction<void>* timer2_callback; // A pointer to a callback taking no parameters.
|
||||
etl::ifunction<void>* timer3_callback; // A pointer to a callback taking no parameters.
|
||||
etl::ifunction<char>* uart1_rx_callback; // A pointer to a callback taking a char parameter.
|
||||
etl::ifunction<char>* uart2_rx_callback; // A pointer to a callback taking a char parameter.
|
||||
|
||||
extern "C"
|
||||
{
|
||||
// Function called from the timer1 interrupt vector.
|
||||
void Timer1Interrupt()
|
||||
{
|
||||
(*timer1_callback)();
|
||||
}
|
||||
|
||||
// Function called from the timer2 interrupt vector.
|
||||
void Timer2Interrupt()
|
||||
{
|
||||
(*timer2_callback)();
|
||||
}
|
||||
|
||||
// Function called from the timer3 interrupt vector.
|
||||
void Timer3Interrupt()
|
||||
{
|
||||
(*timer3_callback)();
|
||||
}
|
||||
|
||||
// Function called from the UART1 rx interrupt vector.
|
||||
void Uart1RxInterrupt()
|
||||
{
|
||||
(*uart1_rx_callback)(get_char());
|
||||
}
|
||||
|
||||
// Function called from the UART2 rx interrupt vector.
|
||||
void Uart2RxInterrupt()
|
||||
{
|
||||
(*uart2_rx_callback)(get_char());
|
||||
}
|
||||
}
|
||||
|
||||
//********************************
|
||||
// Timer driver.
|
||||
//********************************
|
||||
class Timer
|
||||
{
|
||||
public:
|
||||
|
||||
// Constructor.
|
||||
Timer()
|
||||
{
|
||||
}
|
||||
|
||||
// Handler for interrupts from the timer.
|
||||
void MemberTimerInterruptHandler()
|
||||
{
|
||||
std::cout << "Timer interrupt (member)\n";
|
||||
}
|
||||
|
||||
// Static handler for interrupts from the timer.
|
||||
static void StaticTimerInterruptHandler()
|
||||
{
|
||||
std::cout << "Timer interrupt (static)\n";
|
||||
}
|
||||
};
|
||||
|
||||
//********************************
|
||||
// Free function timer driver.
|
||||
//********************************
|
||||
void FreeTimerInterruptHandler()
|
||||
{
|
||||
std::cout << "Timer interrupt (free)\n";
|
||||
}
|
||||
|
||||
etl::function_fv<FreeTimerInterruptHandler> free_callback;
|
||||
|
||||
//********************************
|
||||
// UART driver.
|
||||
//********************************
|
||||
class Uart
|
||||
{
|
||||
public:
|
||||
|
||||
// Constructor.
|
||||
Uart(int port_id)
|
||||
: port_id(port_id)
|
||||
{
|
||||
}
|
||||
|
||||
// Handler for rx interrupts from the UART.
|
||||
void RxInterruptHandler(char c)
|
||||
{
|
||||
std::cout << "UART" << port_id << " Rx char interrupt : Received '" << c << "'\n";
|
||||
}
|
||||
|
||||
int port_id;
|
||||
};
|
||||
|
||||
// Declare the driver instances.
|
||||
Timer timer;
|
||||
Uart uart1(0);
|
||||
Uart uart2(1);
|
||||
|
||||
etl::function_imv<Timer, timer, &Timer::MemberTimerInterruptHandler> timer_member_callback;
|
||||
etl::function_fv<&Timer::StaticTimerInterruptHandler> timer_static_callback;
|
||||
etl::function_imp<Uart, char, uart1, &Uart::RxInterruptHandler> uart1_callback;
|
||||
etl::function_imp<Uart, char, uart2, &Uart::RxInterruptHandler> uart2_callback;
|
||||
|
||||
//********************************
|
||||
// Test it out.
|
||||
//********************************
|
||||
int main()
|
||||
{
|
||||
// Setup the callbacks.
|
||||
// This may be part of the cross platform interface.
|
||||
timer1_callback = &timer_member_callback;
|
||||
timer2_callback = &timer_static_callback;
|
||||
timer3_callback = &free_callback;
|
||||
uart1_rx_callback = &uart1_callback;
|
||||
uart2_rx_callback = &uart2_callback;
|
||||
|
||||
// Simulate the interrupts.
|
||||
Timer1Interrupt();
|
||||
Timer2Interrupt();
|
||||
Timer3Interrupt();
|
||||
Uart1RxInterrupt();
|
||||
Uart2RxInterrupt();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
3
examples/FunctionInterruptSimulation/etl_user.h
Normal file
3
examples/FunctionInterruptSimulation/etl_user.h
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
#define ETL_THROW_EXCEPTIONS
|
||||
#define ETL_VERBOSE_ERRORS
|
||||
@ -0,0 +1,31 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26730.16
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FunctionInterruptSimulation", "FunctionInterruptSimulation.vcxproj", "{2873A2D7-5112-4941-83AA-F98751BE5289}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{2873A2D7-5112-4941-83AA-F98751BE5289}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{2873A2D7-5112-4941-83AA-F98751BE5289}.Debug|x64.Build.0 = Debug|x64
|
||||
{2873A2D7-5112-4941-83AA-F98751BE5289}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{2873A2D7-5112-4941-83AA-F98751BE5289}.Debug|x86.Build.0 = Debug|Win32
|
||||
{2873A2D7-5112-4941-83AA-F98751BE5289}.Release|x64.ActiveCfg = Release|x64
|
||||
{2873A2D7-5112-4941-83AA-F98751BE5289}.Release|x64.Build.0 = Release|x64
|
||||
{2873A2D7-5112-4941-83AA-F98751BE5289}.Release|x86.ActiveCfg = Release|Win32
|
||||
{2873A2D7-5112-4941-83AA-F98751BE5289}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {C8193019-E546-4904-A25F-27FC525CC526}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@ -0,0 +1,155 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>15.0</VCProjectVersion>
|
||||
<ProjectGuid>{2873A2D7-5112-4941-83AA-F98751BE5289}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>FunctionInterruptSimulation</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0.15063.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<AdditionalIncludeDirectories>../../../src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\FunctionInterruptSimulation.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\etl_user.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\FunctionInterruptSimulation.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\etl_user.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@ -295,6 +295,61 @@ namespace etl
|
||||
TObject* p_object; ///< Pointer to the object that contains the function.
|
||||
};
|
||||
|
||||
//***************************************************************************
|
||||
///\ingroup function
|
||||
/// A derived function template that takes an object type and parameter type.
|
||||
///\tparam TObject The object type that contains the member function.
|
||||
///\tparam TParameter The parameter type accepted by the member function.
|
||||
//***************************************************************************
|
||||
template <typename TObject, typename TParameter, TObject& Instance, void (TObject::*Function)(TParameter)>
|
||||
class function_imp : public ifunction<TParameter>
|
||||
{
|
||||
public:
|
||||
|
||||
typedef TObject object_type; ///< The type of object.
|
||||
typedef TParameter parameter_type; ///< The type of parameter sent to the function.
|
||||
|
||||
//*************************************************************************
|
||||
/// The function operator that calls the destination function.
|
||||
///\param data The data to pass to the function.
|
||||
//*************************************************************************
|
||||
virtual void operator ()(TParameter data)
|
||||
{
|
||||
// Call the object's member function with the data.
|
||||
(Instance.*Function)(data);
|
||||
}
|
||||
};
|
||||
|
||||
//***************************************************************************
|
||||
///\ingroup function
|
||||
/// A derived function template that takes an object type and parameter type.
|
||||
///\tparam TObject The object type that contains the member function.
|
||||
///\tparam TParameter The parameter type accepted by the member function.
|
||||
//***************************************************************************
|
||||
template <typename TObject, TObject& Instance, void (TObject::*Function)(void)>
|
||||
class function_imv : public ifunction<void>
|
||||
{
|
||||
public:
|
||||
|
||||
typedef TObject object_type; ///< The type of object.
|
||||
typedef void parameter_type; ///< The type of parameter sent to the function.
|
||||
|
||||
//*************************************************************************
|
||||
/// Constructor.
|
||||
///\param object Reference to the object
|
||||
//*************************************************************************
|
||||
|
||||
//*************************************************************************
|
||||
/// The function operator that calls the destination function.
|
||||
///\param data The data to pass to the function.
|
||||
//*************************************************************************
|
||||
virtual void operator ()()
|
||||
{
|
||||
// Call the object's member function.
|
||||
(Instance.*Function)();
|
||||
}
|
||||
};
|
||||
|
||||
//***************************************************************************
|
||||
///\ingroup function
|
||||
/// A derived function template that takes a parameter type.
|
||||
|
||||
@ -120,6 +120,8 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
Test test_static;
|
||||
|
||||
//*****************************************************************************
|
||||
// Initialises the test results.
|
||||
//*****************************************************************************
|
||||
@ -269,5 +271,41 @@ namespace
|
||||
CHECK(function_called);
|
||||
CHECK(parameter_correct);
|
||||
}
|
||||
|
||||
//*************************************************************************
|
||||
TEST_FIXTURE(SetupFixture, test_instance_member_void_compile_time)
|
||||
{
|
||||
function_called = false;
|
||||
|
||||
etl::function_imv<Test, test_static, &Test::member_void> function;
|
||||
|
||||
call(function);
|
||||
|
||||
CHECK(function_called);
|
||||
}
|
||||
|
||||
//*************************************************************************
|
||||
TEST_FIXTURE(SetupFixture, test_instance_member_parameter_compile_time)
|
||||
{
|
||||
function_called = false;
|
||||
|
||||
etl::function_imp<Test, int, test_static, &Test::member_int> function;
|
||||
|
||||
call(function);
|
||||
|
||||
CHECK(function_called);
|
||||
}
|
||||
|
||||
//*************************************************************************
|
||||
TEST_FIXTURE(SetupFixture, test_instance_member_reference_compile_time)
|
||||
{
|
||||
function_called = false;
|
||||
|
||||
etl::function_imp<Test, const Data&, test_static, &Test::member_reference> function;
|
||||
|
||||
call(function);
|
||||
|
||||
CHECK(function_called);
|
||||
}
|
||||
};
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user