Skip to content

Commit

Permalink
constexpr hash function
Browse files Browse the repository at this point in the history
  • Loading branch information
TorstenRobitzki committed Sep 29, 2016
1 parent 65825d9 commit eb2ae7b
Show file tree
Hide file tree
Showing 4 changed files with 225 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@
*.exe
*.out
*.app

build/
16 changes: 16 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 2.8.7)

include_directories(${CMAKE_SOURCE_DIR})

#set(Boost_DEBUG 1)
find_package( Boost REQUIRED )
if (Boost_FOUND)
include_directories(BEFORE ${Boost_INCLUDE_DIR})
endif()

set(CMAKE_CXX_FLAGS "-std=c++11 -Wall -pedantic -Wextra -Wfatal-errors -Wno-unused-parameter -Wno-sign-compare")

add_executable(tests tests.cpp)
add_custom_target(
tests.run
COMMAND tests)
108 changes: 108 additions & 0 deletions assert_hash.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#ifndef ASSERT_HASH_HPP_INCLUDE_GUARD
#define ASSERT_HASH_HPP_INCLUDE_GUARD

#include <climits>

namespace assert_hash {

using line_number_t = unsigned;

namespace details {

constexpr const char* end_of_string( const char* const str )
{
return *str == 0
? str
: end_of_string( str + 1 );
}

constexpr const char* file_base_impl( const char* const file, const char* const end_of_file, const unsigned directories )
{
return directories == 0 || end_of_file == file
? end_of_file
: ( *end_of_file == '\\' || *end_of_file == '/'
? (
directories == 1
? end_of_file
: file_base_impl( file, end_of_file - 1, directories - 1 )
)
: file_base_impl( file, end_of_file - 1, directories )
);
}

template < typename T >
struct check_checksum_type
{
static_assert( std::is_integral< T >::value, "hash type must be integral" );
static_assert( std::is_unsigned< T >::value, "hash type must be unsigned" );
};

template < typename T >
constexpr T add_char( const T value, const char* const file, unsigned exponent )
{
return value + ( *file << ( CHAR_BIT * exponent ) );
}

template < typename T >
constexpr unsigned next_exponent( unsigned exponent )
{
return exponent + 1 == sizeof( T )
? 0
: exponent + 1;
}

template < typename T >
constexpr T file_hash_impl( const T value, const char* const file, unsigned exponent )
{
return *file == 0
? value
: file_hash_impl( add_char< T >( value, file, exponent ), file + 1, next_exponent< T >( exponent ) );
}

// to prevent "warning: shift count >= width of type [-Wshift-count-overflow]"
template < typename T >
constexpr line_number_t shift_line( const line_number_t line )
{
return sizeof( line ) <= sizeof( T )
? 0
: line >> ( CHAR_BIT * sizeof( T ) );
}

template < typename T >
constexpr T line_hash_impl( const T value, const line_number_t line )
{
return line == 0
? value
: line_hash_impl< T >( value + static_cast< T >( line ), shift_line< T >( line ) );
}
}

constexpr const char* file_base( const char* const file, const unsigned directories = 0 )
{
return directories == 0
? file
: details::file_base_impl( file, details::end_of_string( file ), directories );
}

template < typename T >
constexpr T file_hash( const char* const file )
{
return static_cast< void >( details::check_checksum_type< T >{} ),
details::file_hash_impl< T >( 0, file, 0 );
}

template < typename T >
constexpr T line_hash( const line_number_t line )
{
return static_cast< void >( details::check_checksum_type< T >{} ),
details::line_hash_impl< T >( 0 , line );
}

template < typename T >
constexpr T file_and_line_hash( const char* const file, const line_number_t line, const unsigned directories = 0 )
{
return file_hash< T >( file_base( file, directories ) ) + line_hash< T >( line );
}
}

#endif
99 changes: 99 additions & 0 deletions tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#define BOOST_TEST_MODULE
#include <boost/test/included/unit_test.hpp>

#include <assert_hash.hpp>

namespace ah = assert_hash;

BOOST_AUTO_TEST_CASE( file_base_0 )
{
BOOST_CHECK_EQUAL( ah::file_base( "a" ), "a" );
BOOST_CHECK_EQUAL( ah::file_base( "a.txt" ), "a.txt" );
BOOST_CHECK_EQUAL( ah::file_base( ".a.txt" ), ".a.txt" );
BOOST_CHECK_EQUAL( ah::file_base( "c:\\asd\\.a.txt" ), "c:\\asd\\.a.txt" );
}

BOOST_AUTO_TEST_CASE( end_of_string )
{
const char* const c = "asdasd";
BOOST_CHECK_EQUAL( ah::details::end_of_string( c ) - c, 6 );
BOOST_CHECK_EQUAL( ah::details::end_of_string( c + 6 ) - c, 6 );
}

BOOST_AUTO_TEST_CASE( file_base_1 )
{
BOOST_CHECK_EQUAL( ah::file_base( "a", 1 ), "a" );
BOOST_CHECK_EQUAL( ah::file_base( "a.txt", 1 ), "a.txt" );
BOOST_CHECK_EQUAL( ah::file_base( ".a.txt", 1 ), ".a.txt" );
BOOST_CHECK_EQUAL( ah::file_base( "dev/null", 1 ), "/null" );
BOOST_CHECK_EQUAL( ah::file_base( "/Users/todi/assert_hash/build", 1 ), "/build" );
BOOST_CHECK_EQUAL( ah::file_base( "c:\\asda\\asdasd\\asd\\.a.txt", 1 ), "\\.a.txt" );
}

BOOST_AUTO_TEST_CASE( file_base_2 )
{
BOOST_CHECK_EQUAL( ah::file_base( "a", 2 ), "a" );
BOOST_CHECK_EQUAL( ah::file_base( "a.txt", 2 ), "a.txt" );
BOOST_CHECK_EQUAL( ah::file_base( ".a.txt", 2 ), ".a.txt" );
BOOST_CHECK_EQUAL( ah::file_base( "dev/null", 2 ), "dev/null" );
BOOST_CHECK_EQUAL( ah::file_base( "/Users/todi/assert_hash/build", 2 ), "/assert_hash/build" );
BOOST_CHECK_EQUAL( ah::file_base( "c:\\asda\\asdasd\\asd\\.a.txt", 2 ), "\\asd\\.a.txt" );
}

BOOST_AUTO_TEST_CASE( file_hash_16 )
{
BOOST_CHECK_EQUAL( ah::file_hash< std::uint16_t >( "" ), 0 );
BOOST_CHECK_EQUAL( ah::file_hash< std::uint16_t >( "a" ), 'a' );
BOOST_CHECK_EQUAL( ah::file_hash< std::uint16_t >( "ab" ), 'a' + ( 'b' << 8 ) );
BOOST_CHECK_EQUAL( ah::file_hash< std::uint16_t >( "abc" ), ( 'a' + ( 'b' << 8 ) + 'c' ) & 0xffff );
BOOST_CHECK_EQUAL( ah::file_hash< std::uint16_t >( "abcd" ),
( 'a' + ( 'b' << 8 ) + 'c' + ( 'd' << 8 ) ) & 0xffff );
}

BOOST_AUTO_TEST_CASE( file_hash_8 )
{
BOOST_CHECK_EQUAL( ah::file_hash< std::uint8_t >( "" ), 0 );
BOOST_CHECK_EQUAL( ah::file_hash< std::uint8_t >( "a" ), 'a' );
BOOST_CHECK_EQUAL( ah::file_hash< std::uint8_t >( "ab" ), 'a' + 'b' );
BOOST_CHECK_EQUAL( ah::file_hash< std::uint8_t >( "abc" ), ( 'a' + 'b' + 'c' ) & 0xff );
}

BOOST_AUTO_TEST_CASE( line_hash_8 )
{
BOOST_CHECK_EQUAL( ah::line_hash< std::uint8_t >( 0 ), 0 );
BOOST_CHECK_EQUAL( ah::line_hash< std::uint8_t >( 0x12 ), 0x12 );
BOOST_CHECK_EQUAL( ah::line_hash< std::uint8_t >( 0x12345678 ), ( 0x78 + 0x56 + 0x34 + 0x12 ) & 0xff );
}

BOOST_AUTO_TEST_CASE( line_hash_16 )
{
BOOST_CHECK_EQUAL( ah::line_hash< std::uint16_t >( 0 ), 0 );
BOOST_CHECK_EQUAL( ah::line_hash< std::uint16_t >( 0x12 ), 0x12 );
BOOST_CHECK_EQUAL( ah::line_hash< std::uint16_t >( 0x12345678 ), ( 0x5678 + 0x1234 ) & 0xffff );
}

BOOST_AUTO_TEST_CASE( line_hash_32 )
{
BOOST_CHECK_EQUAL( ah::line_hash< std::uint32_t >( 0 ), 0 );
BOOST_CHECK_EQUAL( ah::line_hash< std::uint32_t >( 0x12 ), 0x12 );
BOOST_CHECK_EQUAL( ah::line_hash< std::uint32_t >( 0x12345678 ), 0x12345678 );
}

BOOST_AUTO_TEST_CASE( file_and_line_hash )
{
constexpr std::uint16_t hash1 = ah::file_and_line_hash< std::uint16_t >( __FILE__, __LINE__ );
constexpr std::uint16_t hash2 = ah::file_and_line_hash< std::uint16_t >( __FILE__, __LINE__ );

BOOST_CHECK_NE( hash1, hash2 );
}

template < const int >
struct force_const {};

BOOST_AUTO_TEST_CASE( check_const_expr )
{
static constexpr const char* c = "test";
force_const< ah::file_base( c, 1 ) - c >();
force_const< ah::file_hash< unsigned >( c ) >();
force_const< ah::file_and_line_hash< std::uint16_t >( __FILE__, __LINE__ ) >();
}

0 comments on commit eb2ae7b

Please sign in to comment.