This can be solved by selecting the last row using the ROW_NUMBER windowed partition function.
WITH last_item_price (item_id, price, reverse_order)
AS
  (SELECT item_id
   , price
   , ROW_NUMBER() OVER 
     (PARTITION BY item_id 
     ORDER BY date DESC) AS reverse_order
  FROM item_price)
SELECT i.name
  , p.price
FROM items i
JOIN last_item_price p
  ON i.item_id = p.item_id
  AND p.reverse_order = 1;
Unless the primary key of item_price is defined as the composite key for item_id and date, there is a risk you will have more than one possible price.  For instance, if the price changes three times on a single day, then how do you know which is the correct value?  I would recommend using a DATETIME or DATETIME2 field to help pick the correct price in that scenario.  Alternatively, you can define the primary key as the composite of the two fields and only allow a single price each day.
Another way to avoid the duplication issue is to add an auto-incrementing identity column.  This will not be useful for joins and I still strongly recommend using a composite key for the item_id and date fields, but it is a valid alternative.  In that case, you can modify the partition function's ORDER BY clause to:
ORDER BY date DESC, item_price_id DESC
In general, I would not recommend naming a field date.  Since SQL Server has a data type called DATE, this can cause issues with linters.  In some cases, you may be required to use brackets so the field is [date] to disambiguate your intent.  A better name is probably price_date or price_change_date.